Cycles: constant folding for RGB/Vector Curves and Color Ramp.
[blender.git] / intern / cycles / render / nodes.cpp
index 6d6284b1b2bba964df923367f934ac5333b8b29a..b3b010841e99a388378f2f0feb26b37c86f8c2e1 100644 (file)
 #include "nodes.h"
 #include "scene.h"
 #include "svm.h"
+#include "svm_color_util.h"
+#include "svm_ramp_util.h"
 #include "svm_math_util.h"
 #include "osl.h"
+#include "constant_fold.h"
 
 #include "util_sky_model.h"
 #include "util_foreach.h"
@@ -397,7 +400,7 @@ void ImageTextureNode::compile(OSLCompiler& compiler)
                compiler.parameter("color_space", "sRGB");
        compiler.parameter(this, "projection");
        compiler.parameter(this, "projection_blend");
-       compiler.parameter(this, "is_float");
+       compiler.parameter("is_float", is_float);
        compiler.parameter("use_alpha", !alpha_out->links.empty());
        compiler.parameter(this, "interpolation");
        compiler.parameter(this, "extension");
@@ -580,7 +583,7 @@ void EnvironmentTextureNode::compile(OSLCompiler& compiler)
                compiler.parameter("color_space", "sRGB");
 
        compiler.parameter(this, "interpolation");
-       compiler.parameter(this, "is_float");
+       compiler.parameter("is_float", is_float);
        compiler.parameter("use_alpha", !alpha_out->links.empty());
        compiler.add(this, "node_environment_texture");
 }
@@ -610,10 +613,10 @@ static float sky_perez_function(float lam[6], float theta, float gamma)
 static void sky_texture_precompute_old(SunSky *sunsky, float3 dir, float turbidity)
 {
        /*
-       * We re-use the SunSky struct of the new model, to avoid extra variables
-       * zenith_Y/x/y is now radiance_x/y/z
-       * perez_Y/x/y is now config_x/y/z
-       */
+        * We re-use the SunSky struct of the new model, to avoid extra variables
+        * zenith_Y/x/y is now radiance_x/y/z
+        * perez_Y/x/y is now config_x/y/z
+        */
        
        float2 spherical = sky_spherical_coordinates(dir);
        float theta = spherical.x;
@@ -1564,8 +1567,8 @@ NODE_DEFINE(RGBToBWNode)
 {
        NodeType* type = NodeType::add("rgb_to_bw", create, NodeType::SHADER);
 
-       SOCKET_IN_POINT(color, "Color", make_float3(0.0f, 0.0f, 0.0f));
-       SOCKET_OUT_POINT(val, "Val");
+       SOCKET_IN_COLOR(color, "Color", make_float3(0.0f, 0.0f, 0.0f));
+       SOCKET_OUT_FLOAT(val, "Val");
 
        return type;
 }
@@ -1575,14 +1578,11 @@ RGBToBWNode::RGBToBWNode()
 {
 }
 
-bool RGBToBWNode::constant_fold(ShaderGraph *, ShaderOutput *, ShaderInput *optimized)
+void RGBToBWNode::constant_fold(const ConstantFolder& folder)
 {
-       if(inputs[0]->link == NULL) {
-               optimized->set(linear_rgb_to_gray(color));
-               return true;
+       if(folder.all_inputs_constant()) {
+               folder.make_constant(linear_rgb_to_gray(color));
        }
-
-       return false;
 }
 
 void RGBToBWNode::compile(SVMCompiler& compiler)
@@ -1595,7 +1595,7 @@ void RGBToBWNode::compile(SVMCompiler& compiler)
 
 void RGBToBWNode::compile(OSLCompiler& compiler)
 {
-       compiler.add(this, "node_convert_from_color");
+       compiler.add(this, "node_rgb_to_bw");
 }
 
 /* Convert */
@@ -1660,40 +1660,35 @@ ConvertNode::ConvertNode(SocketType::Type from_, SocketType::Type to_, bool auto
                special_type = SHADER_SPECIAL_TYPE_AUTOCONVERT;
 }
 
-bool ConvertNode::constant_fold(ShaderGraph *, ShaderOutput *, ShaderInput *optimized)
+void ConvertNode::constant_fold(const ConstantFolder& folder)
 {
        /* proxy nodes should have been removed at this point */
        assert(special_type != SHADER_SPECIAL_TYPE_PROXY);
 
-       ShaderInput *in = inputs[0];
-
        /* TODO(DingTo): conversion from/to int is not supported yet, don't fold in that case */
 
-       if(in->link == NULL) {
+       if(folder.all_inputs_constant()) {
                if(from == SocketType::FLOAT) {
                        if(SocketType::is_float3(to)) {
-                               optimized->set(make_float3(value_float, value_float, value_float));
-                               return true;
+                               folder.make_constant(make_float3(value_float, value_float, value_float));
                        }
                }
                else if(SocketType::is_float3(from)) {
                        if(to == SocketType::FLOAT) {
-                               if(from == SocketType::COLOR)
+                               if(from == SocketType::COLOR) {
                                        /* color to float */
-                                       optimized->set(linear_rgb_to_gray(value_color));
-                               else
+                                       folder.make_constant(linear_rgb_to_gray(value_color));
+                               }
+                               else {
                                        /* vector/point/normal to float */
-                                       optimized->set(average(value_vector));
-                               return true;
+                                       folder.make_constant(average(value_vector));
+                               }
                        }
                        else if(SocketType::is_float3(to)) {
-                               optimized->set(value_color);
-                               return true;
+                               folder.make_constant(value_color);
                        }
                }
        }
-
-       return false;
 }
 
 void ConvertNode::compile(SVMCompiler& compiler)
@@ -1829,6 +1824,7 @@ NODE_DEFINE(AnisotropicBsdfNode)
        static NodeEnum distribution_enum;
        distribution_enum.insert("beckmann", CLOSURE_BSDF_MICROFACET_BECKMANN_ANISO_ID);
        distribution_enum.insert("GGX", CLOSURE_BSDF_MICROFACET_GGX_ANISO_ID);
+       distribution_enum.insert("Multiscatter GGX", CLOSURE_BSDF_MICROFACET_MULTI_GGX_ANISO_ID);
        distribution_enum.insert("ashikhmin_shirley", CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ANISO_ID);
        SOCKET_ENUM(distribution, "Distribution", distribution_enum, CLOSURE_BSDF_MICROFACET_GGX_ANISO_ID);
 
@@ -1865,7 +1861,10 @@ void AnisotropicBsdfNode::compile(SVMCompiler& compiler)
 {
        closure = distribution;
 
-       BsdfNode::compile(compiler, input("Roughness"), input("Anisotropy"), input("Rotation"));
+       if(closure == CLOSURE_BSDF_MICROFACET_MULTI_GGX_ANISO_ID)
+               BsdfNode::compile(compiler, input("Roughness"), input("Anisotropy"), input("Rotation"), input("Color"));
+       else
+               BsdfNode::compile(compiler, input("Roughness"), input("Anisotropy"), input("Rotation"));
 }
 
 void AnisotropicBsdfNode::compile(OSLCompiler& compiler)
@@ -1889,6 +1888,7 @@ NODE_DEFINE(GlossyBsdfNode)
        distribution_enum.insert("beckmann", CLOSURE_BSDF_MICROFACET_BECKMANN_ID);
        distribution_enum.insert("GGX", CLOSURE_BSDF_MICROFACET_GGX_ID);
        distribution_enum.insert("ashikhmin_shirley", CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID);
+       distribution_enum.insert("Multiscatter GGX", CLOSURE_BSDF_MICROFACET_MULTI_GGX_ID);
        SOCKET_ENUM(distribution, "Distribution", distribution_enum, CLOSURE_BSDF_MICROFACET_GGX_ID);
        SOCKET_IN_FLOAT(roughness, "Roughness", 0.2f);
 
@@ -1938,6 +1938,8 @@ void GlossyBsdfNode::compile(SVMCompiler& compiler)
 
        if(closure == CLOSURE_BSDF_REFLECTION_ID)
                BsdfNode::compile(compiler, NULL, NULL);
+       else if(closure == CLOSURE_BSDF_MICROFACET_MULTI_GGX_ID)
+               BsdfNode::compile(compiler, input("Roughness"), NULL, input("Color"));
        else
                BsdfNode::compile(compiler, input("Roughness"), NULL);
 }
@@ -1962,6 +1964,7 @@ NODE_DEFINE(GlassBsdfNode)
        distribution_enum.insert("sharp", CLOSURE_BSDF_SHARP_GLASS_ID);
        distribution_enum.insert("beckmann", CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID);
        distribution_enum.insert("GGX", CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID);
+       distribution_enum.insert("Multiscatter GGX", CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID);
        SOCKET_ENUM(distribution, "Distribution", distribution_enum, CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID);
        SOCKET_IN_FLOAT(roughness, "Roughness", 0.0f);
        SOCKET_IN_FLOAT(IOR, "IOR", 0.3f);
@@ -2012,6 +2015,8 @@ void GlassBsdfNode::compile(SVMCompiler& compiler)
 
        if(closure == CLOSURE_BSDF_SHARP_GLASS_ID)
                BsdfNode::compile(compiler, NULL, input("IOR"));
+       else if(closure == CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID)
+               BsdfNode::compile(compiler, input("Roughness"), input("IOR"), input("Color"));
        else
                BsdfNode::compile(compiler, input("Roughness"), input("IOR"));
 }
@@ -2291,6 +2296,7 @@ NODE_DEFINE(SubsurfaceScatteringNode)
 SubsurfaceScatteringNode::SubsurfaceScatteringNode()
 : BsdfNode(node_type)
 {
+       closure = falloff;
 }
 
 void SubsurfaceScatteringNode::compile(SVMCompiler& compiler)
@@ -2301,6 +2307,7 @@ void SubsurfaceScatteringNode::compile(SVMCompiler& compiler)
 
 void SubsurfaceScatteringNode::compile(OSLCompiler& compiler)
 {
+       closure = falloff;
        compiler.parameter(this, "falloff");
        compiler.add(this, "node_subsurface_scattering");
 }
@@ -2319,7 +2326,7 @@ NODE_DEFINE(EmissionNode)
        NodeType* type = NodeType::add("emission", create, NodeType::SHADER);
 
        SOCKET_IN_COLOR(color, "Color", make_float3(0.8f, 0.8f, 0.8f));
-       SOCKET_IN_FLOAT(strength, "Strength", 1.0f);
+       SOCKET_IN_FLOAT(strength, "Strength", 10.0f);
        SOCKET_IN_FLOAT(surface_mix_weight, "SurfaceMixWeight", 0.0f, SocketType::SVM_INTERNAL);
 
        SOCKET_OUT_CLOSURE(emission, "Emission");
@@ -2340,7 +2347,7 @@ void EmissionNode::compile(SVMCompiler& compiler)
        if(color_in->link || strength_in->link) {
                compiler.add_node(NODE_EMISSION_WEIGHT,
                                  compiler.stack_assign(color_in),
-                                                 compiler.stack_assign(strength_in));
+                                 compiler.stack_assign(strength_in));
        }
        else
                compiler.add_node(NODE_CLOSURE_SET_WEIGHT, color * strength);
@@ -2353,13 +2360,15 @@ void EmissionNode::compile(OSLCompiler& compiler)
        compiler.add(this, "node_emission");
 }
 
-bool EmissionNode::constant_fold(ShaderGraph *, ShaderOutput *, ShaderInput *)
+void EmissionNode::constant_fold(const ConstantFolder& folder)
 {
        ShaderInput *color_in = input("Color");
        ShaderInput *strength_in = input("Strength");
 
-       return ((!color_in->link && color == make_float3(0.0f, 0.0f, 0.0f)) ||
-               (!strength_in->link && strength == 0.0f));
+       if ((!color_in->link && color == make_float3(0.0f, 0.0f, 0.0f)) ||
+           (!strength_in->link && strength == 0.0f)) {
+               folder.discard();
+       }
 }
 
 /* Background Closure */
@@ -2403,13 +2412,15 @@ void BackgroundNode::compile(OSLCompiler& compiler)
        compiler.add(this, "node_background");
 }
 
-bool BackgroundNode::constant_fold(ShaderGraph *, ShaderOutput *, ShaderInput *)
+void BackgroundNode::constant_fold(const ConstantFolder& folder)
 {
        ShaderInput *color_in = input("Color");
        ShaderInput *strength_in = input("Strength");
 
-       return ((!color_in->link && color == make_float3(0.0f, 0.0f, 0.0f)) ||
-               (!strength_in->link && strength == 0.0f));
+       if ((!color_in->link && color == make_float3(0.0f, 0.0f, 0.0f)) ||
+           (!strength_in->link && strength == 0.0f)) {
+               folder.discard();
+       }
 }
 
 /* Holdout Closure */
@@ -2985,7 +2996,7 @@ NODE_DEFINE(LightPathNode)
 
        SOCKET_OUT_FLOAT(is_camera_ray, "Is Camera Ray");
        SOCKET_OUT_FLOAT(is_shadow_ray, "Is Shadow Ray");
-       SOCKET_OUT_FLOAT(is_diffus_ray, "Is Diffuse Ray");
+       SOCKET_OUT_FLOAT(is_diffuse_ray, "Is Diffuse Ray");
        SOCKET_OUT_FLOAT(is_glossy_ray, "Is Glossy Ray");
        SOCKET_OUT_FLOAT(is_singular_ray, "Is Singular Ray");
        SOCKET_OUT_FLOAT(is_reflection_ray, "Is Reflection Ray");
@@ -3371,10 +3382,9 @@ ValueNode::ValueNode()
 {
 }
 
-bool ValueNode::constant_fold(ShaderGraph *, ShaderOutput *, ShaderInput *optimized)
+void ValueNode::constant_fold(const ConstantFolder& folder)
 {
-       optimized->set(value);
-       return true;
+       folder.make_constant(value);
 }
 
 void ValueNode::compile(SVMCompiler& compiler)
@@ -3407,10 +3417,9 @@ ColorNode::ColorNode()
 {
 }
 
-bool ColorNode::constant_fold(ShaderGraph *, ShaderOutput *, ShaderInput *optimized)
+void ColorNode::constant_fold(const ConstantFolder& folder)
 {
-       optimized->set(value);
-       return true;
+       folder.make_constant(value);
 }
 
 void ColorNode::compile(SVMCompiler& compiler)
@@ -3459,6 +3468,20 @@ void AddClosureNode::compile(OSLCompiler& compiler)
        compiler.add(this, "node_add_closure");
 }
 
+void AddClosureNode::constant_fold(const ConstantFolder& folder)
+{
+       ShaderInput *closure1_in = input("Closure1");
+       ShaderInput *closure2_in = input("Closure2");
+
+       /* remove useless add closures nodes */
+       if(!closure1_in->link) {
+               folder.bypass_or_discard(closure2_in);
+       }
+       else if(!closure2_in->link) {
+               folder.bypass_or_discard(closure1_in);
+       }
+}
+
 /* Mix Closure */
 
 NODE_DEFINE(MixClosureNode)
@@ -3490,35 +3513,28 @@ void MixClosureNode::compile(OSLCompiler& compiler)
        compiler.add(this, "node_mix_closure");
 }
 
-bool MixClosureNode::constant_fold(ShaderGraph *graph, ShaderOutput *, ShaderInput *)
+void MixClosureNode::constant_fold(const ConstantFolder& folder)
 {
        ShaderInput *fac_in = input("Fac");
        ShaderInput *closure1_in = input("Closure1");
        ShaderInput *closure2_in = input("Closure2");
-       ShaderOutput *closure_out = output("Closure");
 
        /* remove useless mix closures nodes */
        if(closure1_in->link == closure2_in->link) {
-               graph->relink(this, closure_out, closure1_in->link);
-               return true;
+               folder.bypass_or_discard(closure1_in);
        }
-
-       /* remove unused mix closure input when factor is 0.0 or 1.0 */
-       /* check for closure links and make sure factor link is disconnected */
-       if(closure1_in->link && closure2_in->link && !fac_in->link) {
+       /* remove unused mix closure input when factor is 0.0 or 1.0
+        * check for closure links and make sure factor link is disconnected */
+       else if(!fac_in->link) {
                /* factor 0.0 */
-               if(fac == 0.0f) {
-                       graph->relink(this, closure_out, closure1_in->link);
-                       return true;
+               if(fac <= 0.0f) {
+                       folder.bypass_or_discard(closure1_in);
                }
                /* factor 1.0 */
-               else if(fac == 1.0f) {
-                       graph->relink(this, closure_out, closure2_in->link);
-                       return true;
+               else if(fac >= 1.0f) {
+                       folder.bypass_or_discard(closure2_in);
                }
        }
-
-       return false;
 }
 
 /* Mix Closure */
@@ -3580,6 +3596,23 @@ InvertNode::InvertNode()
 {
 }
 
+void InvertNode::constant_fold(const ConstantFolder& folder)
+{
+       ShaderInput *fac_in = input("Fac");
+       ShaderInput *color_in = input("Color");
+
+       if(!fac_in->link) {
+               /* evaluate fully constant node */
+               if(!color_in->link) {
+                       folder.make_constant(interp(color, make_float3(1.0f, 1.0f, 1.0f) - color, fac));
+               }
+               /* remove no-op node */
+               else if(fac == 0.0f) {
+                       folder.bypass(color_in->link);
+               }
+       }
+}
+
 void InvertNode::compile(SVMCompiler& compiler)
 {
        ShaderInput *fac_in = input("Fac");
@@ -3666,44 +3699,47 @@ void MixNode::compile(OSLCompiler& compiler)
        compiler.add(this, "node_mix");
 }
 
-bool MixNode::constant_fold(ShaderGraph *graph, ShaderOutput *, ShaderInput *optimized)
+void MixNode::constant_fold(const ConstantFolder& folder)
 {
-       if(type != NODE_MIX_BLEND) {
-               return false;
-       }
-
        ShaderInput *fac_in = input("Fac");
        ShaderInput *color1_in = input("Color1");
        ShaderInput *color2_in = input("Color2");
-       ShaderOutput *color_out = output("Color");
 
-       /* remove useless mix colors nodes */
-       if(color1_in->link && color1_in->link == color2_in->link) {
-               graph->relink(this, color_out, color1_in->link);
-               return true;
+       /* evaluate fully constant node */
+       if(folder.all_inputs_constant()) {
+               folder.make_constant_clamp(svm_mix(type, fac, color1, color2), use_clamp);
+               return;
        }
 
-       /* remove unused mix color input when factor is 0.0 or 1.0 */
-       if(!fac_in->link) {
-               /* factor 0.0 */
-               if(fac == 0.0f) {
-                       if(color1_in->link)
-                               graph->relink(this, color_out, color1_in->link);
-                       else
-                               optimized->set(color1);
-                       return true;
+       /* remove no-op node when factor is 0.0 */
+       if(!fac_in->link && fac <= 0.0f) {
+               /* note that some of the modes will clamp out of bounds values even without use_clamp */
+               if(type == NODE_MIX_LIGHT || type == NODE_MIX_DODGE || type == NODE_MIX_BURN) {
+                       if(!color1_in->link) {
+                               folder.make_constant_clamp(svm_mix(type, 0.0f, color1, color1), use_clamp);
+                               return;
+                       }
                }
-               /* factor 1.0 */
-               else if(fac == 1.0f) {
-                       if(color2_in->link)
-                               graph->relink(this, color_out, color2_in->link);
-                       else
-                               optimized->set(color2);
-                       return true;
+               else if(folder.try_bypass_or_make_constant(color1_in, color1, use_clamp)) {
+                       return;
                }
        }
 
-       return false;
+       if(type == NODE_MIX_BLEND) {
+               /* remove useless mix colors nodes */
+               if(color1_in->link ? (color1_in->link == color2_in->link) : (!color2_in->link && color1 == color2)) {
+                       if(folder.try_bypass_or_make_constant(color1_in, color1, use_clamp)) {
+                               return;
+                       }
+               }
+
+               /* remove no-op mix color node when factor is 1.0 */
+               if(!fac_in->link && fac >= 1.0f) {
+                       if(folder.try_bypass_or_make_constant(color2_in, color2, use_clamp)) {
+                               return;
+                       }
+               }
+       }
 }
 
 /* Combine RGB */
@@ -3726,6 +3762,13 @@ CombineRGBNode::CombineRGBNode()
 {
 }
 
+void CombineRGBNode::constant_fold(const ConstantFolder& folder)
+{
+       if(folder.all_inputs_constant()) {
+               folder.make_constant(make_float3(r, g, b));
+       }
+}
+
 void CombineRGBNode::compile(SVMCompiler& compiler)
 {
        ShaderInput *red_in = input("R");
@@ -3761,7 +3804,7 @@ NODE_DEFINE(CombineXYZNode)
        SOCKET_IN_FLOAT(y, "Y", 0.0f);
        SOCKET_IN_FLOAT(z, "Z", 0.0f);
 
-       SOCKET_OUT_COLOR(color, "Image");
+       SOCKET_OUT_VECTOR(vector, "Vector");
 
        return type;
 }
@@ -3771,6 +3814,13 @@ CombineXYZNode::CombineXYZNode()
 {
 }
 
+void CombineXYZNode::constant_fold(const ConstantFolder& folder)
+{
+       if(folder.all_inputs_constant()) {
+               folder.make_constant(make_float3(x, y, z));
+       }
+}
+
 void CombineXYZNode::compile(SVMCompiler& compiler)
 {
        ShaderInput *x_in = input("X");
@@ -3816,6 +3866,13 @@ CombineHSVNode::CombineHSVNode()
 {
 }
 
+void CombineHSVNode::constant_fold(const ConstantFolder& folder)
+{
+       if(folder.all_inputs_constant()) {
+               folder.make_constant(hsv_to_rgb(make_float3(h, s, v)));
+       }
+}
+
 void CombineHSVNode::compile(SVMCompiler& compiler)
 {
        ShaderInput *hue_in = input("H");
@@ -3854,17 +3911,11 @@ GammaNode::GammaNode()
 {
 }
 
-bool GammaNode::constant_fold(ShaderGraph *, ShaderOutput *, ShaderInput *optimized)
+void GammaNode::constant_fold(const ConstantFolder& folder)
 {
-       ShaderInput *color_in = input("Color");
-       ShaderInput *gamma_in = input("Gamma");
-
-       if(color_in->link == NULL && gamma_in->link == NULL) {
-               optimized->set(svm_math_gamma_color(color, gamma));
-               return true;
+       if(folder.all_inputs_constant()) {
+               folder.make_constant(svm_math_gamma_color(color, gamma));
        }
-
-       return false;
 }
 
 void GammaNode::compile(SVMCompiler& compiler)
@@ -3904,6 +3955,13 @@ BrightContrastNode::BrightContrastNode()
 {
 }
 
+void BrightContrastNode::constant_fold(const ConstantFolder& folder)
+{
+       if(folder.all_inputs_constant()) {
+               folder.make_constant(svm_brightness_contrast(color, bright, contrast));
+       }
+}
+
 void BrightContrastNode::compile(SVMCompiler& compiler)
 {
        ShaderInput *color_in = input("Color");
@@ -3944,6 +4002,18 @@ SeparateRGBNode::SeparateRGBNode()
 {
 }
 
+void SeparateRGBNode::constant_fold(const ConstantFolder& folder)
+{
+       if(folder.all_inputs_constant()) {
+               for(int channel = 0; channel < 3; channel++) {
+                       if(outputs[channel] == folder.output) {
+                               folder.make_constant(color[channel]);
+                               return;
+                       }
+               }
+       }
+}
+
 void SeparateRGBNode::compile(SVMCompiler& compiler)
 {
        ShaderInput *color_in = input("Image");
@@ -3989,6 +4059,18 @@ SeparateXYZNode::SeparateXYZNode()
 {
 }
 
+void SeparateXYZNode::constant_fold(const ConstantFolder& folder)
+{
+       if(folder.all_inputs_constant()) {
+               for(int channel = 0; channel < 3; channel++) {
+                       if(outputs[channel] == folder.output) {
+                               folder.make_constant(vector[channel]);
+                               return;
+                       }
+               }
+       }
+}
+
 void SeparateXYZNode::compile(SVMCompiler& compiler)
 {
        ShaderInput *vector_in = input("Vector");
@@ -4034,6 +4116,20 @@ SeparateHSVNode::SeparateHSVNode()
 {
 }
 
+void SeparateHSVNode::constant_fold(const ConstantFolder& folder)
+{
+       if(folder.all_inputs_constant()) {
+               float3 hsv = rgb_to_hsv(color);
+
+               for(int channel = 0; channel < 3; channel++) {
+                       if(outputs[channel] == folder.output) {
+                               folder.make_constant(hsv[channel]);
+                               return;
+                       }
+               }
+       }
+}
+
 void SeparateHSVNode::compile(SVMCompiler& compiler)
 {
        ShaderInput *color_in = input("Color");
@@ -4417,16 +4513,11 @@ BlackbodyNode::BlackbodyNode()
 {
 }
 
-bool BlackbodyNode::constant_fold(ShaderGraph *, ShaderOutput *, ShaderInput *optimized)
+void BlackbodyNode::constant_fold(const ConstantFolder& folder)
 {
-       ShaderInput *temperature_in = input("Temperature");
-
-       if(temperature_in->link == NULL) {
-               optimized->set(svm_math_blackbody_color(temperature));
-               return true;
+       if(folder.all_inputs_constant()) {
+               folder.make_constant(svm_math_blackbody_color(temperature));
        }
-
-       return false;
 }
 
 void BlackbodyNode::compile(SVMCompiler& compiler)
@@ -4528,24 +4619,11 @@ MathNode::MathNode()
 {
 }
 
-bool MathNode::constant_fold(ShaderGraph *, ShaderOutput *, ShaderInput *optimized)
+void MathNode::constant_fold(const ConstantFolder& folder)
 {
-       ShaderInput *value1_in = input("Value1");
-       ShaderInput *value2_in = input("Value2");
-
-       if(value1_in->link == NULL && value2_in->link == NULL) {
-               float value = svm_math(type, value1, value2);
-
-               if(use_clamp) {
-                       value = saturate(value);
-               }
-
-               optimized->set(value);
-
-               return true;
+       if(folder.all_inputs_constant()) {
+               folder.make_constant_clamp(svm_math(type, value1, value2), use_clamp);
        }
-
-       return false;
 }
 
 void MathNode::compile(SVMCompiler& compiler)
@@ -4599,32 +4677,25 @@ VectorMathNode::VectorMathNode()
 {
 }
 
-bool VectorMathNode::constant_fold(ShaderGraph *, ShaderOutput *socket, ShaderInput *optimized)
+void VectorMathNode::constant_fold(const ConstantFolder& folder)
 {
-       ShaderInput *vector1_in = input("Vector1");
-       ShaderInput *vector2_in = input("Vector2");
-
        float value;
        float3 vector;
 
-       if(vector1_in->link == NULL && vector2_in->link == NULL) {
+       if(folder.all_inputs_constant()) {
                svm_vector_math(&value,
                                &vector,
                                type,
                                vector1,
                                vector2);
 
-               if(socket == output("Value")) {
-                       optimized->set(value);
-                       return true;
+               if(folder.output == output("Value")) {
+                       folder.make_constant(value);
                }
-               else if(socket == output("Vector")) {
-                       optimized->set(vector);
-                       return true;
+               else if(folder.output == output("Vector")) {
+                       folder.make_constant(vector);
                }
        }
-
-       return false;
 }
 
 void VectorMathNode::compile(SVMCompiler& compiler)
@@ -4758,7 +4829,7 @@ void BumpNode::compile(OSLCompiler& compiler)
        compiler.add(this, "node_bump");
 }
 
-bool BumpNode::constant_fold(ShaderGraph *graph, ShaderOutput *, ShaderInput *)
+void BumpNode::constant_fold(const ConstantFolder& folder)
 {
        ShaderInput *height_in = input("Height");
        ShaderInput *normal_in = input("Normal");
@@ -4766,56 +4837,60 @@ bool BumpNode::constant_fold(ShaderGraph *graph, ShaderOutput *, ShaderInput *)
        if(height_in->link == NULL) {
                if(normal_in->link == NULL) {
                        GeometryNode *geom = new GeometryNode();
-                       graph->add(geom);
-                       graph->relink(this, outputs[0], geom->output("Normal"));
+                       folder.graph->add(geom);
+                       folder.bypass(geom->output("Normal"));
                }
                else {
-                       graph->relink(this, outputs[0], normal_in->link);
+                       folder.bypass(normal_in->link);
                }
-               return true;
        }
 
        /* TODO(sergey): Ignore bump with zero strength. */
-
-       return false;
 }
 
-/* RGBCurvesNode */
 
-NODE_DEFINE(RGBCurvesNode)
+/* Curve node */
+
+CurvesNode::CurvesNode(const NodeType *node_type)
+: ShaderNode(node_type)
 {
-       NodeType* type = NodeType::add("rgb_curves", create, NodeType::SHADER);
+}
 
-       SOCKET_COLOR_ARRAY(curves, "Curves", array<float3>());
-       SOCKET_FLOAT(min_x, "Min X", 0.0f);
-       SOCKET_FLOAT(max_x, "Max X", 1.0f);
+void CurvesNode::constant_fold(const ConstantFolder& folder, ShaderInput *value_in)
+{
+       ShaderInput *fac_in = input("Fac");
 
-       SOCKET_IN_FLOAT(fac, "Fac", 0.0f);
-       SOCKET_IN_COLOR(color, "Color", make_float3(0.0f, 0.0f, 0.0f));
+       /* remove no-op node */
+       if(!fac_in->link && fac == 0.0f) {
+               folder.bypass(value_in->link);
+       }
+       /* evaluate fully constant node */
+       else if(folder.all_inputs_constant()) {
+               if (curves.size() == 0)
+                       return;
 
-       SOCKET_OUT_COLOR(color, "Color");
+               float3 pos = (value - make_float3(min_x, min_x, min_x)) / (max_x - min_x);
+               float3 result;
 
-       return type;
-}
+               result[0] = rgb_ramp_lookup(curves.data(), pos[0], true, true, curves.size()).x;
+               result[1] = rgb_ramp_lookup(curves.data(), pos[1], true, true, curves.size()).y;
+               result[2] = rgb_ramp_lookup(curves.data(), pos[2], true, true, curves.size()).z;
 
-RGBCurvesNode::RGBCurvesNode()
-: ShaderNode(node_type)
-{
+               folder.make_constant(interp(value, result, fac));
+       }
 }
 
-void RGBCurvesNode::compile(SVMCompiler& compiler)
+void CurvesNode::compile(SVMCompiler& compiler, int type, ShaderInput *value_in, ShaderOutput *value_out)
 {
        if(curves.size() == 0)
                return;
 
        ShaderInput *fac_in = input("Fac");
-       ShaderInput *color_in = input("Color");
-       ShaderOutput *color_out = output("Color");
 
-       compiler.add_node(NODE_RGB_CURVES,
+       compiler.add_node(type,
                          compiler.encode_uchar4(compiler.stack_assign(fac_in),
-                                                compiler.stack_assign(color_in),
-                                                compiler.stack_assign(color_out)),
+                                                compiler.stack_assign(value_in),
+                                                compiler.stack_assign(value_out)),
                          __float_as_int(min_x),
                          __float_as_int(max_x));
 
@@ -4824,7 +4899,7 @@ void RGBCurvesNode::compile(SVMCompiler& compiler)
                compiler.add_node(float3_to_float4(curves[i]));
 }
 
-void RGBCurvesNode::compile(OSLCompiler& compiler)
+void CurvesNode::compile(OSLCompiler& compiler, const char* name)
 {
        if(curves.size() == 0)
                return;
@@ -4832,7 +4907,55 @@ void RGBCurvesNode::compile(OSLCompiler& compiler)
        compiler.parameter_color_array("ramp", curves);
        compiler.parameter(this, "min_x");
        compiler.parameter(this, "max_x");
-       compiler.add(this, "node_rgb_curves");
+       compiler.add(this, name);
+}
+
+void CurvesNode::compile(SVMCompiler& /*compiler*/)
+{
+       assert(0);
+}
+
+void CurvesNode::compile(OSLCompiler& /*compiler*/)
+{
+       assert(0);
+}
+
+/* RGBCurvesNode */
+
+NODE_DEFINE(RGBCurvesNode)
+{
+       NodeType* type = NodeType::add("rgb_curves", create, NodeType::SHADER);
+
+       SOCKET_COLOR_ARRAY(curves, "Curves", array<float3>());
+       SOCKET_FLOAT(min_x, "Min X", 0.0f);
+       SOCKET_FLOAT(max_x, "Max X", 1.0f);
+
+       SOCKET_IN_FLOAT(fac, "Fac", 0.0f);
+       SOCKET_IN_COLOR(value, "Color", make_float3(0.0f, 0.0f, 0.0f));
+
+       SOCKET_OUT_COLOR(value, "Color");
+
+       return type;
+}
+
+RGBCurvesNode::RGBCurvesNode()
+: CurvesNode(node_type)
+{
+}
+
+void RGBCurvesNode::constant_fold(const ConstantFolder& folder)
+{
+       CurvesNode::constant_fold(folder, input("Color"));
+}
+
+void RGBCurvesNode::compile(SVMCompiler& compiler)
+{
+       CurvesNode::compile(compiler, NODE_RGB_CURVES, input("Color"), output("Color"));
+}
+
+void RGBCurvesNode::compile(OSLCompiler& compiler)
+{
+       CurvesNode::compile(compiler, "node_rgb_curves");
 }
 
 /* VectorCurvesNode */
@@ -4846,48 +4969,31 @@ NODE_DEFINE(VectorCurvesNode)
        SOCKET_FLOAT(max_x, "Max X", 1.0f);
 
        SOCKET_IN_FLOAT(fac, "Fac", 0.0f);
-       SOCKET_IN_VECTOR(vector, "Vector", make_float3(0.0f, 0.0f, 0.0f));
+       SOCKET_IN_VECTOR(value, "Vector", make_float3(0.0f, 0.0f, 0.0f));
 
-       SOCKET_OUT_VECTOR(vector, "Vector");
+       SOCKET_OUT_VECTOR(value, "Vector");
 
        return type;
 }
 
 VectorCurvesNode::VectorCurvesNode()
-: ShaderNode(node_type)
+: CurvesNode(node_type)
 {
 }
 
-void VectorCurvesNode::compile(SVMCompiler& compiler)
+void VectorCurvesNode::constant_fold(const ConstantFolder& folder)
 {
-       if(curves.size() == 0)
-               return;
-
-       ShaderInput *fac_in = input("Fac");
-       ShaderInput *vector_in = input("Vector");
-       ShaderOutput *vector_out = output("Vector");
-
-       compiler.add_node(NODE_VECTOR_CURVES,
-                         compiler.encode_uchar4(compiler.stack_assign(fac_in),
-                                                compiler.stack_assign(vector_in),
-                                                compiler.stack_assign(vector_out)),
-                         __float_as_int(min_x),
-                         __float_as_int(max_x));
+       CurvesNode::constant_fold(folder, input("Vector"));
+}
 
-       compiler.add_node(curves.size());
-       for(int i = 0; i < curves.size(); i++)
-               compiler.add_node(float3_to_float4(curves[i]));
+void VectorCurvesNode::compile(SVMCompiler& compiler)
+{
+       CurvesNode::compile(compiler, NODE_VECTOR_CURVES, input("Vector"), output("Vector"));
 }
 
 void VectorCurvesNode::compile(OSLCompiler& compiler)
 {
-       if(curves.size() == 0)
-               return;
-
-       compiler.parameter_color_array("ramp", curves);
-       compiler.parameter(this, "min_x");
-       compiler.parameter(this, "max_x");
-       compiler.add(this, "node_vector_curves");
+       CurvesNode::compile(compiler, "node_vector_curves");
 }
 
 /* RGBRampNode */
@@ -4913,6 +5019,31 @@ RGBRampNode::RGBRampNode()
 {
 }
 
+void RGBRampNode::constant_fold(const ConstantFolder& folder)
+{
+       if(ramp.size() == 0 || ramp.size() != ramp_alpha.size())
+               return;
+
+       if(folder.all_inputs_constant()) {
+               float f = clamp(fac, 0.0f, 1.0f) * (ramp.size() - 1);
+
+               /* clamp int as well in case of NaN */
+               int i = clamp((int)f, 0, ramp.size()-1);
+               float t = f - (float)i;
+
+               bool use_lerp = interpolate && t > 0.0f;
+
+               if(folder.output == output("Color")) {
+                       float3 color = rgb_ramp_lookup(ramp.data(), fac, use_lerp, false, ramp.size());
+                       folder.make_constant(color);
+               }
+               else if(folder.output == output("Alpha")) {
+                       float alpha = float_ramp_lookup(ramp_alpha.data(), fac, use_lerp, false, ramp_alpha.size());
+                       folder.make_constant(alpha);
+               }
+       }
+}
+
 void RGBRampNode::compile(SVMCompiler& compiler)
 {
        if(ramp.size() == 0 || ramp.size() != ramp_alpha.size())
@@ -4991,7 +5122,12 @@ OSLNode::~OSLNode()
        delete type;
 }
 
-OSLNode* OSLNode::create(size_t num_inputs)
+ShaderNode *OSLNode::clone() const
+{
+       return OSLNode::create(this->inputs.size(), this);
+}
+
+OSLNode* OSLNode::create(size_t num_inputs, const OSLNode *from)
 {
        /* allocate space for the node itself and parameters, aligned to 16 bytes
         * assuming that's the most parameter types need */
@@ -5001,7 +5137,17 @@ OSLNode* OSLNode::create(size_t num_inputs)
        char *node_memory = (char*) operator new(node_size + inputs_size);
        memset(node_memory, 0, node_size + inputs_size);
 
-       return new(node_memory) OSLNode();
+       if (!from) {
+               return new(node_memory) OSLNode();
+       }
+       else {
+               /* copy input default values and node type for cloning */
+               memcpy(node_memory + node_size, (char*)from + node_size, inputs_size);
+
+               OSLNode *node = new(node_memory) OSLNode(*from);
+               node->type = new NodeType(*(from->type));
+               return node;
+       }
 }
 
 char* OSLNode::input_default_value()