From 9034e81679eeeaa3d1d5d643d3f924d9edb45a68 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Thu, 30 Aug 2012 00:48:58 +0300 Subject: [PATCH] Entirely new system for building standard shaders 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 | 2 +- demos/shaders.cpp | 2 +- source/program.cpp | 200 +-------------- source/program.h | 29 +-- source/programbuilder.cpp | 497 ++++++++++++++++++++++++++++++++++++++ source/programbuilder.h | 108 +++++++++ 6 files changed, 615 insertions(+), 223 deletions(-) create mode 100644 source/programbuilder.cpp create mode 100644 source/programbuilder.h diff --git a/demos/cubemap.cpp b/demos/cubemap.cpp index 98d0ba24..512f519f 100644 --- a/demos/cubemap.cpp +++ b/demos/cubemap.cpp @@ -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; diff --git a/demos/shaders.cpp b/demos/shaders.cpp index 1b9c6472..73ae1b72 100644 --- a/demos/shaders.cpp +++ b/demos/shaders.cpp @@ -67,7 +67,7 @@ int main() vector 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; diff --git a/source/program.cpp b/source/program.cpp index 84b3cdd1..636988d7 100644 --- a/source/program.cpp +++ b/source/program.cpp @@ -13,100 +13,6 @@ 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(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(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 diff --git a/source/program.h b/source/program.h index 21b38c25..8cdfb6de 100644 --- a/source/program.h +++ b/source/program.h @@ -6,6 +6,7 @@ #include #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 - { - 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 index 00000000..46f7c153 --- /dev/null +++ b/source/programbuilder.cpp @@ -0,0 +1,497 @@ +#include +#include +#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 variables; + list 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::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 identifiers = extract_identifiers(i->expression); + for(vector::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::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 &variables, VariableScope scope) const +{ + string source; + + for(list::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::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 varyings; + for(list::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::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::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 ProgramBuilder::extract_identifiers(const char *expression) +{ + vector 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 &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::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::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::iterator i=referenced_by.begin(); i!=referenced_by.end(); ++i) + (*i)->resolve_space(space); + } + + for(list::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::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 replace_map; + for(list::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(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 index 00000000..30f1ef15 --- /dev/null +++ b/source/programbuilder.h @@ -0,0 +1,108 @@ +#ifndef MSP_GL_PROGRAMBUILDER_H_ +#define MSP_GL_PROGRAMBUILDER_H_ + +#include +#include +#include +#include + +namespace Msp { +namespace GL { + +class Program; + +class ProgramBuilder +{ +public: + struct StandardFeatures + { + class Loader: public DataFile::ObjectLoader + { + 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 referenced_vars; + std::list 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 &, 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 extract_identifiers(const char *); + static std::string replace_identifiers(const char *, const std::map &); +}; + +} // namespace GL +} // namespace Msp + +#endif -- 2.43.0