]> git.tdb.fi Git - libs/gl.git/commitdiff
Entirely new system for building standard shaders
authorMikko Rasa <tdb@tdb.fi>
Wed, 29 Aug 2012 21:48:58 +0000 (00:48 +0300)
committerMikko Rasa <tdb@tdb.fi>
Wed, 29 Aug 2012 21:48:58 +0000 (00:48 +0300)
The old one was becoming such a huge mess that I had to spend two minutes
doing boolean analysis to figure out the conditions for a multiplication
operator.  The logic parser was also insufficient for expressing
sophisticated relationships between variables.

The new one operates with expression, starting from a target variable and
pulling in others to compute its value.  The logic parser is still there,
buts its burden is greatly reduced, as most of the work is done by the
dependency resolver.

Reflections are missing at the moment, but will be added back soon.  They
were pretty broken in the old system anyway, with the assumption that the
environment map is in eye space.

The generated shader code is rather verbose, containing a lot more local
variables than are needed.  An optimizer will be implemented in the near
future.

demos/cubemap.cpp
demos/shaders.cpp
source/program.cpp
source/program.h
source/programbuilder.cpp [new file with mode: 0644]
source/programbuilder.h [new file with mode: 0644]

index 98d0ba245f0ab8bab0c6d4a7cd35a7f603bc8280..512f519f260d619450dd9f60f938ec1f2db0dffa 100644 (file)
@@ -84,7 +84,7 @@ int main()
        material.set_specular(GL::Color(1.0));
        material.set_shininess(100);
 
-       GL::Program::StandardFeatures features;
+       GL::ProgramBuilder::StandardFeatures features;
        features.lighting = true;
        features.specular = true;
        features.material = true;
index 1b9c64724f78bd394ab9051458bfcfe496136d63..73ae1b721321696ddc3114bfbb97676df16e00bd 100644 (file)
@@ -67,7 +67,7 @@ int main()
        vector<GL::Program *> programs;
        for(unsigned i=0; i<12; ++i)
        {
-               GL::Program::StandardFeatures feat;
+               GL::ProgramBuilder::StandardFeatures feat;
                feat.material = i/4>0;
                feat.texture = i/4>1;
                feat.lighting = i%4>0;
index 84b3cdd16f4e5e2f6fdfbca6c07f812917f3f07d..636988d75c75dd11f3728906cf3c761b31553b34 100644 (file)
 
 using namespace std;
 
-namespace {
-
-const char *standard_vertex_src[] =
-{
-       "s",   "uniform int shadow_unit;\n",
-       "n",   "attribute vec3 tangent;\n",
-       "n",   "attribute vec3 binormal;\n",
-       "t|n", "varying vec2 v_texcoord;\n",
-       "s",   "varying vec3 v_shadowcoord;\n",
-       "!lm", "varying vec4 v_color;\n",
-       "l!n", "varying vec3 v_normal;\n",
-       "l",   "varying vec3 v_light_dir;\n",
-       "p|e", "varying vec3 v_eye_dir;\n",
-       "r",   "vec4 transform_vertex(vec4);\n",
-       "lr",  "vec3 transform_normal(vec3);\n",
-        0,    "void main()\n",
-        0,    "{\n",
-       "r",   "\tvec4 eye_pos = transform_vertex(gl_Vertex);\n",
-       "!r",  "\tvec4 eye_pos = gl_ModelViewMatrix*gl_Vertex;\n",
-        0,    "\tgl_Position = gl_ProjectionMatrix*eye_pos;\n",
-       "lr",  "\tvec3 eye_normal = transform_normal(gl_Normal);\n",
-       "l!r", "\tvec3 eye_normal = gl_NormalMatrix*gl_Normal;\n",
-       "l!n", "\tv_normal = eye_normal;\n",
-       "nr",  "\tvec3 eye_tangent = transform_normal(tangent);\n",
-       "n!r", "\tvec3 eye_tangent = gl_NormalMatrix*tangent;\n",
-       "nr",  "\tvec3 eye_binormal = transform_normal(binormal);\n",
-       "n!r", "\tvec3 eye_binormal = gl_NormalMatrix*binormal;\n",
-       "l",   "\tvec3 eye_light_dir = normalize(gl_LightSource[0].position.xyz-eye_pos.xyz*gl_LightSource[0].position.w);\n",
-       "n",   "\tv_light_dir = vec3(dot(eye_tangent, eye_light_dir), dot(eye_binormal, eye_light_dir), dot(eye_normal, eye_light_dir));\n",
-       "l!n", "\tv_light_dir = eye_light_dir;\n",
-       "p|e", "\tvec3 eye_dir = -normalize(eye_pos.xyz);\n",
-       "p|en",  "\tv_eye_dir = vec3(dot(eye_tangent, eye_dir), dot(eye_binormal, eye_dir), dot(eye_normal, eye_dir));\n",
-       "p|e!n", "\tv_eye_dir = eye_dir;\n",
-       "t|n", "\tv_texcoord = gl_MultiTexCoord0.xy;\n",
-       "s",   "\tv_shadowcoord = vec3(dot(gl_EyePlaneS[shadow_unit], eye_pos), dot(gl_EyePlaneT[shadow_unit], eye_pos), dot(gl_EyePlaneR[shadow_unit], eye_pos));\n",
-       "!lm", "\tv_color = gl_Color;\n",
-        0,    "}",
-       0, 0
-};
-
-const char *standard_fragment_src[] =
-{
-       "t",   "uniform sampler2D texture;\n",
-       "n",   "uniform sampler2D normalmap;\n",
-       "s",   "uniform sampler2DShadow shadow;\n",
-       "e",   "uniform samplerCube environment;\n",
-       "e",   "uniform float reflectivity;\n",
-       "t|n", "varying vec2 v_texcoord;\n",
-       "s",   "varying vec3 v_shadowcoord;\n",
-       "!lm", "varying vec4 v_color;\n",
-       "l!n", "varying vec3 v_normal;\n",
-       "l",   "varying vec3 v_light_dir;\n",
-       "p|e", "varying vec3 v_eye_dir;\n",
-        0,    "void main()\n",
-        0,    "{\n",
-       "n",   "\tvec3 n_normal = texture2D(normalmap, v_texcoord).xyz*2.0-1.0;\n",
-       "l!n", "\tvec3 n_normal = normalize(v_normal);\n",
-       "l",   "\tfloat l_diffuse = max(dot(n_normal, normalize(v_light_dir)), 0.0);\n",
-       "p",   "\tvec3 half_dir = normalize(v_eye_dir+v_light_dir);\n",
-       "p",   "\tfloat l_specular = pow(max(dot(half_dir, n_normal), 0.0), gl_FrontMaterial.shininess);\n",
-       "s",   "\tfloat l_shadow = shadow2D(shadow, v_shadowcoord).r;\n",
-       /* XXX This is incorrect with normal mapping, since the vectors are in TBN
-       space but environment map is expected to be in eye space */
-       "e",   "\tvec4 reflection = textureCube(environment, n_normal*(dot(n_normal, v_eye_dir)*2.0)-v_eye_dir);\n",
-       "t",   "\tvec4 tex_sample = texture2D(texture, v_texcoord);\n",
-        0,    "\tgl_FragColor.rgb = ",
-       "!t!m",  "vec3(1.0)",
-       "t",     "tex_sample.rgb",
-       "l|mt",  "*",
-       "l!m!t",  "*",
-       "!lm",   "v_color.rgb",
-       "l",     "((l_diffuse",
-       "lm",    "*gl_FrontLightProduct[0].diffuse.rgb",
-       "p",     "+l_specular",
-       "pm",    "*gl_FrontLightProduct[0].specular.rgb",
-       "l",     ")",
-       "s",     "*l_shadow",
-       "lm",    "+gl_FrontLightModelProduct.sceneColor.rgb",
-       "l",     ")",
-       "e",     "+reflection.rgb*reflectivity",
-        0,      ";\n",
-        0,    "\tgl_FragColor.a = ",
-       "!t!m",  "1.0",
-       "t",     "tex_sample.a",
-       "tm",    "*",
-       "!lm",   "v_color.a",
-       "lm",    "gl_FrontMaterial.diffuse.a",
-        0,      ";\n",
-        0,    "}\n",
-       0, 0
-};
-
-}
-
 namespace Msp {
 namespace GL {
 
@@ -115,11 +21,12 @@ Program::Program()
        init();
 }
 
-Program::Program(const StandardFeatures &features)
+Program::Program(const ProgramBuilder::StandardFeatures &features)
 {
        init();
 
-       add_standard_shaders(features);
+       ProgramBuilder builder(features);
+       builder.add_shaders(*this);
        if(!features.transform)
                link();
 }
@@ -174,52 +81,6 @@ void Program::detach_shader(Shader &shader)
        }
 }
 
-void Program::add_standard_shaders(const StandardFeatures &features)
-{
-       string flags = features.create_flags();
-       string vertex_src = process_standard_source(standard_vertex_src, flags);
-       string fragment_src = process_standard_source(standard_fragment_src, flags);
-       attach_shader_owned(new VertexShader(vertex_src));
-       attach_shader_owned(new FragmentShader(fragment_src));
-}
-
-string Program::process_standard_source(const char **source, const string &flags)
-{
-       string result;
-
-       for(unsigned i=0; source[i+1]; i+=2)
-       {
-               if(source[i])
-               {
-                       bool cond = true;
-                       char oper = '&';
-                       for(const char *c=source[i]; *c; ++c)
-                       {
-                               if(*c>='a' && *c<='z')
-                               {
-                                       bool found = (flags.find(*c)!=string::npos);
-                                       if(oper=='|')
-                                               cond = (cond || found);
-                                       else if(oper=='!')
-                                               cond = (cond && !found);
-                                       else if(oper=='&')
-                                               cond = (cond && found);
-                                       oper = '&';
-                               }
-                               else
-                                       oper = *c;
-                       }
-
-                       if(!cond)
-                               continue;
-               }
-
-               result += source[i+1];
-       }
-
-       return result;
-}
-
 void Program::bind_attribute(unsigned index, const string &name)
 {
        static Require _req(ARB_vertex_shader);
@@ -419,43 +280,6 @@ void Program::unbind()
 }
 
 
-Program::StandardFeatures::StandardFeatures():
-       texture(false),
-       material(false),
-       lighting(false),
-       specular(false),
-       normalmap(false),
-       shadow(false),
-       reflection(false),
-       transform(false)
-{ }
-
-string Program::StandardFeatures::create_flags() const
-{
-       string flags;
-       if(texture)
-               flags += 't';
-       if(material)
-               flags += 'm';
-       if(lighting)
-       {
-               flags += 'l';
-               if(specular)
-                       flags += 'p';
-               if(normalmap)
-                       flags += 'n';
-       }
-       if(shadow)
-               flags += 's';
-       if(reflection)
-               flags += 'e';
-       if(transform)
-               flags += 'r';
-
-       return flags;
-}
-
-
 Program::Loader::Loader(Program &p):
        DataFile::ObjectLoader<Program>(p)
 {
@@ -482,9 +306,10 @@ void Program::Loader::fragment_shader(const string &src)
 
 void Program::Loader::standard()
 {
-       StandardFeatures feat;
+       ProgramBuilder::StandardFeatures feat;
        load_sub(feat);
-       obj.add_standard_shaders(feat);
+       ProgramBuilder builder(feat);
+       builder.add_shaders(obj);
 }
 
 void Program::Loader::vertex_shader(const string &src)
@@ -492,18 +317,5 @@ void Program::Loader::vertex_shader(const string &src)
        obj.attach_shader_owned(new VertexShader(src));
 }
 
-
-Program::StandardFeatures::Loader::Loader(StandardFeatures &f):
-       DataFile::ObjectLoader<StandardFeatures>(f)
-{
-       add("lighting",  &StandardFeatures::lighting);
-       add("material",  &StandardFeatures::material);
-       add("normalmap", &StandardFeatures::normalmap);
-       add("shadow",    &StandardFeatures::shadow);
-       add("specular",  &StandardFeatures::specular);
-       add("texture",   &StandardFeatures::texture);
-       add("transform", &StandardFeatures::transform);
-}
-
 } // namespace GL
 } // namespace Msp
index 21b38c255e7262d77d01800c2204d27c75c7f0f8..8cdfb6de44e561910b0f4e446d0d0095bab76b89 100644 (file)
@@ -6,6 +6,7 @@
 #include <msp/datafile/objectloader.h>
 #include "bindable.h"
 #include "gl.h"
+#include "programbuilder.h"
 
 namespace Msp {
 namespace GL {
@@ -29,28 +30,6 @@ public:
                void vertex_shader(const std::string &);
        };
 
-       struct StandardFeatures
-       {
-               class Loader: public DataFile::ObjectLoader<StandardFeatures>
-               {
-               public:
-                       Loader(StandardFeatures &);
-               };
-
-               bool texture;
-               bool material;
-               bool lighting;
-               bool specular;
-               bool normalmap;
-               bool shadow;
-               bool reflection;
-               bool transform;
-
-               StandardFeatures();
-
-               std::string create_flags() const;
-       };
-
        struct UniformBlockInfo;
 
        struct UniformInfo
@@ -88,7 +67,7 @@ private:
 
 public:
        Program();
-       Program(const StandardFeatures &);
+       Program(const ProgramBuilder::StandardFeatures &);
        Program(const std::string &, const std::string &);
 private:
        void init();
@@ -98,10 +77,6 @@ public:
        void attach_shader(Shader &shader);
        void attach_shader_owned(Shader *shader);
        void detach_shader(Shader &shader);
-       void add_standard_shaders(const StandardFeatures &);
-private:
-       static std::string process_standard_source(const char **, const std::string &);
-public:
        const ShaderList &get_shaders() const { return shaders; }
 
        void bind_attribute(unsigned, const std::string &);
diff --git a/source/programbuilder.cpp b/source/programbuilder.cpp
new file mode 100644 (file)
index 0000000..46f7c15
--- /dev/null
@@ -0,0 +1,497 @@
+#include <algorithm>
+#include <msp/strings/format.h>
+#include "program.h"
+#include "programbuilder.h"
+#include "shader.h"
+
+using namespace std;
+
+namespace Msp {
+namespace GL {
+
+/*
+Naming conventions:
+  n_*        Normalized vector
+  l_*        Lighting component
+
+  obj_*      Object space
+  eye_*      Eye space
+  tbn_*      Tangent-Binormal-Normal space
+  shd_*      Shadow space
+  *_dir      Direction vector
+
+  zzz_*      Wildcard space, resolved by the builder
+             All wildcard spaces within an expression must match
+
+  xxx_yyy_*  Matrix that transforms between yyy to xxx
+             The vector is on the side of its designated space, result will be
+             in the other space
+  *_matrix   A matrix (duh)
+  *_rmatrix  A mat4 that works with a row vector
+
+  rgb_*      Color with rgb components only
+  color_*    Color with rgba components
+*/
+
+/* The array are stored in reverse order, so that variables always come after
+anything that might need them. */
+const ProgramBuilder::StandardVariable ProgramBuilder::standard_variables[] =
+{
+       { FRAGMENT, "gl_FragColor", 0, "color_base", "!t" },
+       { FRAGMENT, "gl_FragColor", 0, "tex_sample*color_base", "t" },
+       { FRAGMENT, "color_base", "vec4", "color_unlit", "!l!s" },
+       { FRAGMENT, "color_base", "vec4", "color_unlit*vec4(vec3(l_shadow), 1.0)", "!ls" },
+       { FRAGMENT, "color_base", "vec4", "vec4(rgb_light_full, 1.0)", "l!m" },
+       { FRAGMENT, "color_base", "vec4", "vec4(rgb_light_full, gl_FrontMaterial.diffuse.a)", "lm" },
+       { FRAGMENT, "color_unlit", "vec4", "vec4(1.0)", "!m" },
+       { FRAGMENT, "color_unlit", "vec4", "color", "m" },
+       { FRAGMENT, "rgb_light_full", "vec3", "rgb_light_shadow+gl_FrontLightModelProduct.sceneColor.rgb", "m" },
+       { FRAGMENT, "rgb_light_full", "vec3", "rgb_light_shadow", "!m" },
+       { 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*gl_FrontLightProduct[0].diffuse.rgb", "m!p" },
+       { FRAGMENT, "rgb_light", "vec3", "l_diffuse*gl_FrontLightProduct[0].diffuse.rgb+l_specular*gl_FrontLightProduct[0].specular.rgb", "mp" },
+       { FRAGMENT, "l_shadow", "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), gl_FrontMaterial.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, "n_tbn_normal", "vec3", "texture2D(normalmap, texture_coord).xyz*2.0-1.0", "n" },
+       { FRAGMENT, "n_eye_normal", "vec3", "normalize(eye_normal)", "!n" },
+       { FRAGMENT, "tex_sample", "vec4", "texture2D(texture, texture_coord)", 0 },
+
+       { VERTEX, "gl_Position", 0, "gl_ProjectionMatrix*eye_vertex", 0 },
+       { VERTEX, "shd_vertex", "vec3", "eye_vertex*eye_shd_rmatrix", 0 },
+       { VERTEX, "eye_shd_rmatrix", "mat4", "mat4(gl_EyePlaneS[shadow_unit], gl_EyePlaneT[shadow_unit], gl_EyePlaneR[shadow_unit], vec4(0.0, 0.0, 0.0, 1.0))", 0 },
+       { VERTEX, "tbn_light_dir", "vec3", "eye_light_dir*eye_tbn_matrix", 0 },
+       { VERTEX, "eye_light_dir", "vec3", "normalize(gl_LightSource[0].position.xyz-eye_vertex.xyz*gl_LightSource[0].position.w)", 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 },
+       { VERTEX, "eye_vertex", "vec4", "gl_ModelViewMatrix*gl_Vertex", "!r" },
+       { VERTEX, "eye_vertex", "vec4", "transform_vertex(gl_Vertex)", "r" },
+       { VERTEX, "eye_normal", "vec3", "gl_NormalMatrix*gl_Normal", "!r" },
+       { VERTEX, "eye_normal", "vec3", "transform_normal(gl_Normal)", "r" },
+       { VERTEX, "eye_tangent", "vec3", "gl_NormalMatrix*tangent", "!r" },
+       { VERTEX, "eye_tangent", "vec3", "transform_normal(tangent)", "r" },
+       { VERTEX, "eye_binormal", "vec3", "gl_NormalMatrix*binormal", "!r" },
+       { VERTEX, "eye_binormal", "vec3", "transform_normal(binormal)", "r" },
+       { VERTEX, "color", "vec4", "gl_Color", 0 },
+       { VERTEX, "texture_coord", "vec2", "gl_MultiTexCoord0", 0 },
+
+       { ATTRIBUTE, "tangent", "vec3", 0, 0 },
+       { ATTRIBUTE, "binormal", "vec3", 0, 0 },
+
+       { UNIFORM, "shadow_unit", "int", 0, 0 },
+       { UNIFORM, "texture", "sampler2D", 0, 0 },
+       { UNIFORM, "shadow", "sampler2DShadow", 0, 0 },
+       { UNIFORM, "normalmap", "sampler2D", 0, 0 },
+
+       // Terminator entry
+       { NO_SCOPE,  0, 0, 0, 0 }
+};
+
+ProgramBuilder::ProgramBuilder(const StandardFeatures &f):
+       features(f),
+       feature_flags(features.create_flags())
+{ }
+
+Program *ProgramBuilder::create_program() const
+{
+       Program *prog = new Program;
+       add_shaders(*prog);
+       return prog;
+}
+
+void ProgramBuilder::add_shaders(Program &prog) const
+{
+       list<ShaderVariable> variables;
+       list<ShaderVariable *> resolved_vars;
+
+       variables.push_front(ShaderVariable("gl_Position"));
+       variables.push_front(ShaderVariable("gl_FragColor"));
+
+       for(const StandardVariable *i=standard_variables; i->name; ++i)
+       {
+               // Skip over anything that isn't used with the supplied flags
+               if(i->flags && !evaluate_flags(i->flags))
+                       continue;
+
+               // See if this variable can satisfy any unresolved variables
+               ShaderVariable *last_resolved = 0;
+               for(list<ShaderVariable>::iterator j=variables.begin(); j!=variables.end(); ++j)
+               {
+                       if(j->variable)
+                               continue;
+
+                       if(!name_match(i->name, j->resolved_name.c_str()))
+                               continue;
+
+                       if(last_resolved)
+                       {
+                               /* We've already resolved a non-fuzzy variable in this iteration.
+                               If there are multiple variables that can be resolved, they refer
+                               to the same variable. */
+                               j->resolve(*last_resolved);
+                               continue;
+                       }
+
+                       j->resolve(*i);
+                       resolved_vars.push_front(&*j);
+                       if(!j->fuzzy_space)
+                               last_resolved = &*j;
+
+                       if(!i->expression)
+                               continue;
+
+                       vector<string> identifiers = extract_identifiers(i->expression);
+                       for(vector<string>::const_iterator k=identifiers.begin(); k!=identifiers.end(); ++k)
+                       {
+                               // Use an existing variable if possible, but only if it's not fuzzy
+                               ShaderVariable *var = 0;
+                               for(list<ShaderVariable>::iterator l=variables.begin(); (!var && l!=variables.end()); ++l)
+                                       if(!l->fuzzy_space && l->resolved_name==*k)
+                                               var = &*l;
+
+                               if(!var)
+                               {
+                                       variables.push_back(ShaderVariable(*k));
+                                       var = &variables.back();
+                               }
+                               j->add_reference(*var);
+                       }
+               }
+       }
+
+       prog.attach_shader_owned(new VertexShader(create_source(resolved_vars, VERTEX)));
+       prog.attach_shader_owned(new FragmentShader(create_source(resolved_vars, FRAGMENT)));
+}
+
+string ProgramBuilder::create_source(const list<ShaderVariable *> &variables, VariableScope scope) const
+{
+       string source;
+
+       for(list<ShaderVariable *>::const_iterator i=variables.begin(); i!=variables.end(); ++i)
+               if((*i)->variable->scope==UNIFORM && (*i)->is_referenced_from(scope))
+                       source += format("uniform %s %s;\n", (*i)->variable->type, (*i)->resolved_name);
+
+       if(scope==VERTEX)
+       {
+               for(list<ShaderVariable *>::const_iterator i=variables.begin(); i!=variables.end(); ++i)
+                       if((*i)->variable->scope==ATTRIBUTE)
+                               source += format("attribute %s %s;\n", (*i)->variable->type, (*i)->resolved_name);
+       }
+
+       /* Any variables defined in vertex scope but referenced from fragment scope
+       should be exported as varyings over the interface. */
+       list<ShaderVariable *> varyings;
+       for(list<ShaderVariable *>::const_iterator i=variables.begin(); i!=variables.end(); ++i)
+               if((*i)->variable->scope==VERTEX && (*i)->is_referenced_from(FRAGMENT))
+               {
+                       varyings.push_back(*i);
+                       source += format("varying %s v_%s;\n", (*i)->variable->type, (*i)->resolved_name);
+               }
+
+       if(scope==VERTEX && features.transform)
+       {
+               // Add the prototypes here, until I come up with something better
+               source += "vec4 transform_vertex(vec4);\n";
+               source += "vec3 transform_normal(vec3);\n";
+       }
+
+       source += "void main()\n{\n";
+
+       for(list<ShaderVariable *>::const_iterator i=variables.begin(); i!=variables.end(); ++i)
+               if((*i)->variable->scope==scope)
+               {
+                       source += '\t';
+                       if((*i)->variable->type)
+                       {
+                               source += (*i)->variable->type;
+                               source += ' ';
+                       }
+                       source += format("%s = %s;\n", (*i)->resolved_name, (*i)->get_expression());
+               }
+
+       if(scope==VERTEX)
+       {
+               for(list<ShaderVariable *>::const_iterator i=varyings.begin(); i!=varyings.end(); ++i)
+                       source += format("\tv_%s = %s;\n", (*i)->resolved_name, (*i)->resolved_name);
+       }
+
+       source += '}';
+
+       return source;
+}
+
+bool ProgramBuilder::evaluate_flags(const char *flags) const
+{
+       if(!flags)
+               return true;
+
+       bool cond = true;
+       char oper = '&';
+       for(const char *i=flags; *i; ++i)
+       {
+               if(*i>='a' && *i<='z')
+               {
+                       bool found = (feature_flags.find(*i)!=string::npos);
+                       if(oper=='|')
+                               cond = (cond || found);
+                       else if(oper=='!')
+                               cond = (cond && !found);
+                       else if(oper=='&')
+                               cond = (cond && found);
+                       oper = '&';
+               }
+               else
+                       oper = *i;
+       }
+
+       return cond;
+}
+
+ProgramBuilder::MatchLevel ProgramBuilder::name_match(const char *n1, const char *n2, const char **space)
+{
+       int i = 0;
+       int zzz = -1;
+       int zside = 0;
+       while(*n1 && *n2)
+       {
+               if(*n1==*n2 || *n1=='z' || *n2=='z')
+               {
+                       if(*n1!=*n2)
+                       {
+                               int side = (*n1=='z' ? 1 : 2);
+                               if(zzz<0)
+                               {
+                                       zzz = i;
+                                       zside = side;
+                                       if(space)
+                                       {
+                                               if(*n1=='z')
+                                                       *space = n2;
+                                               else
+                                                       *space = n1;
+                                       }
+                               }
+                               else if(i>=zzz+3 || side!=zside)
+                                       return NO_MATCH;
+                       }
+               }
+               else
+                       return NO_MATCH;
+               ++n1;
+               ++n2;
+               ++i;
+       }
+       return (!*n1 && !*n2) ? zzz>=0 ? FUZZY : EXACT : NO_MATCH;
+}
+
+bool ProgramBuilder::parse_identifier(const char *ptr, unsigned &start, unsigned &length)
+{
+       bool found = false;
+       bool member = false;
+       for(const char *i=ptr;; ++i)
+       {
+               if(!found)
+               {
+                       if(!*i)
+                               return false;
+                       if(isalpha(*i) || *i=='_')
+                       {
+                               if(!member)
+                               {
+                                       start = i-ptr;
+                                       found = true;
+                               }
+                       }
+                       else if(*i=='.')
+                               member = true;
+                       else
+                               member = false;
+               }
+               else
+               {
+                       if(!isalnum(*i) && *i!='_')
+                       {
+                               length = i-(ptr+start);
+                               return true;
+                       }
+               }
+       }
+}
+
+vector<string> ProgramBuilder::extract_identifiers(const char *expression)
+{
+       vector<string> result;
+       const char *ptr = expression;
+       unsigned start, length;
+       while(parse_identifier(ptr, start, length))
+       {
+               result.push_back(string(ptr+start, length));
+               ptr += start+length;
+       }
+       return result;
+}
+
+string ProgramBuilder::replace_identifiers(const char *expression, const map<string, string> &replace_map)
+{
+       string result;
+       const char *ptr = expression;
+       unsigned start, length;
+       while(parse_identifier(ptr, start, length))
+       {
+               result.append(ptr, start);
+               string identifier(ptr+start, length);
+               map<string, string>::const_iterator i = replace_map.find(identifier);
+               if(i!=replace_map.end())
+                       result += i->second;
+               else
+                       result += identifier;
+               ptr += start+length;
+       }
+       result += ptr;
+       return result;
+}
+
+
+ProgramBuilder::StandardFeatures::StandardFeatures():
+       texture(false),
+       material(false),
+       lighting(false),
+       specular(false),
+       normalmap(false),
+       shadow(false),
+       reflection(false),
+       transform(false)
+{ }
+
+string ProgramBuilder::StandardFeatures::create_flags() const
+{
+       string flags;
+       if(texture)
+               flags += 't';
+       if(material)
+               flags += 'm';
+       if(lighting)
+       {
+               flags += 'l';
+               if(specular)
+                       flags += 'p';
+               if(normalmap)
+                       flags += 'n';
+       }
+       if(shadow)
+               flags += 's';
+       if(reflection)
+               flags += 'e';
+       if(transform)
+               flags += 'r';
+
+       return flags;
+}
+
+
+ProgramBuilder::ShaderVariable::ShaderVariable(const std::string &n):
+       name(n),
+       variable(0),
+       resolved_name(n),
+       fuzzy_space(name.find("zzz")!=string::npos)
+{ }
+
+void ProgramBuilder::ShaderVariable::resolve(const StandardVariable &var)
+{
+       variable = &var;
+       const char *space = 0;
+       if(name_match(var.name, resolved_name.c_str(), &space)==FUZZY)
+               resolve_space(string(space, 3));
+}
+
+void ProgramBuilder::ShaderVariable::resolve(ShaderVariable &var)
+{
+       for(list<ShaderVariable *>::iterator i=referenced_by.begin(); i!=referenced_by.end(); ++i)
+               (*i)->update_reference(*this, var);
+}
+
+void ProgramBuilder::ShaderVariable::resolve_space(const string &space)
+{
+       if(fuzzy_space)
+       {
+               resolved_space = space;
+
+               string::size_type zzz = resolved_name.find("zzz");
+               resolved_name.replace(zzz, 3, resolved_space);
+               fuzzy_space = false;
+
+               // Resolving the space could have affected other variables that use this one
+               for(list<ShaderVariable *>::iterator i=referenced_by.begin(); i!=referenced_by.end(); ++i)
+                       (*i)->resolve_space(space);
+       }
+
+       for(list<ShaderVariable *>::iterator i=referenced_vars.begin(); i!=referenced_vars.end(); ++i)
+               if((*i)->fuzzy_space)
+                       (*i)->resolve_space(space);
+}
+
+void ProgramBuilder::ShaderVariable::add_reference(ShaderVariable &var)
+{
+       referenced_vars.push_back(&var);
+       var.referenced_by.push_back(this);
+       if(var.fuzzy_space && !resolved_space.empty())
+               var.resolve_space(resolved_space);
+}
+
+void ProgramBuilder::ShaderVariable::update_reference(ShaderVariable &from, ShaderVariable &to)
+{
+       replace(referenced_vars.begin(), referenced_vars.end(), &from, &to);
+       replace(referenced_by.begin(), referenced_by.end(), &from, &to);
+       if(from.fuzzy_space && !to.fuzzy_space && !to.resolved_space.empty())
+               resolve_space(to.resolved_space);
+}
+
+bool ProgramBuilder::ShaderVariable::is_referenced_from(VariableScope scope) const
+{
+       for(list<ShaderVariable *>::const_iterator i=referenced_by.begin(); i!=referenced_by.end(); ++i)
+               if((*i)->variable->scope==scope)
+                       return true;
+       return false;
+}
+
+string ProgramBuilder::ShaderVariable::get_expression() const
+{
+       map<string, string> replace_map;
+       for(list<ShaderVariable *>::const_iterator i=referenced_vars.begin(); i!=referenced_vars.end(); ++i)
+               if((*i)->variable)
+               {
+                       string var_name = (*i)->resolved_name;
+                       if(variable->scope==FRAGMENT && (*i)->variable->scope==VERTEX)
+                               var_name = "v_"+var_name;
+                       if(var_name!=(*i)->name)
+                               replace_map[(*i)->name] = var_name;
+               }
+
+       if(replace_map.empty())
+               return variable->expression;
+       else
+               return replace_identifiers(variable->expression, replace_map);
+}
+
+
+ProgramBuilder::StandardFeatures::Loader::Loader(StandardFeatures &f):
+       DataFile::ObjectLoader<StandardFeatures>(f)
+{
+       add("lighting",  &StandardFeatures::lighting);
+       add("material",  &StandardFeatures::material);
+       add("normalmap", &StandardFeatures::normalmap);
+       add("reflection", &StandardFeatures::reflection);
+       add("shadow",    &StandardFeatures::shadow);
+       add("specular",  &StandardFeatures::specular);
+       add("texture",   &StandardFeatures::texture);
+       add("transform", &StandardFeatures::transform);
+}
+
+} // namespace GL
+} // namespace Msp
diff --git a/source/programbuilder.h b/source/programbuilder.h
new file mode 100644 (file)
index 0000000..30f1ef1
--- /dev/null
@@ -0,0 +1,108 @@
+#ifndef MSP_GL_PROGRAMBUILDER_H_
+#define MSP_GL_PROGRAMBUILDER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+#include <msp/datafile/objectloader.h>
+
+namespace Msp {
+namespace GL {
+
+class Program;
+
+class ProgramBuilder
+{
+public:
+       struct StandardFeatures
+       {
+               class Loader: public DataFile::ObjectLoader<StandardFeatures>
+               {
+               public:
+                       Loader(StandardFeatures &);
+               };
+
+               bool texture;
+               bool material;
+               bool lighting;
+               bool specular;
+               bool normalmap;
+               bool shadow;
+               bool reflection;
+               bool transform;
+
+               StandardFeatures();
+
+               std::string create_flags() const;
+       };
+
+       enum VariableScope
+       {
+               NO_SCOPE,
+               UNIFORM,
+               ATTRIBUTE,
+               VERTEX,
+               FRAGMENT
+       };
+
+private:
+       struct StandardVariable
+       {
+               VariableScope scope;
+               const char *name;
+               const char *type;
+               const char *expression;
+               const char *flags;
+       };
+
+       struct ShaderVariable
+       {
+               std::string name;
+               const StandardVariable *variable;
+               std::string resolved_name;
+               bool fuzzy_space;
+               std::string resolved_space;
+               std::list<ShaderVariable *> referenced_vars;
+               std::list<ShaderVariable *> referenced_by;
+
+               ShaderVariable(const std::string &);
+
+               void resolve(const StandardVariable &);
+               void resolve(ShaderVariable &);
+               void resolve_space(const std::string &);
+               void add_reference(ShaderVariable &);
+               void update_reference(ShaderVariable &, ShaderVariable &);
+               bool is_referenced_from(VariableScope) const;
+               std::string get_expression() const;
+       };
+
+       enum MatchLevel
+       {
+               NO_MATCH,
+               EXACT,
+               FUZZY
+       };
+
+       StandardFeatures features;
+       std::string feature_flags;
+
+       static const StandardVariable standard_variables[];
+
+public:
+       ProgramBuilder(const StandardFeatures &);
+
+       Program *create_program() const;
+       void add_shaders(Program &) const;
+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 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> &);
+};
+
+} // namespace GL
+} // namespace Msp
+
+#endif