Cycles OSL: shader script node
authorBrecht Van Lommel <brechtvanlommel@pandora.be>
Sat, 3 Nov 2012 14:32:35 +0000 (14:32 +0000)
committerBrecht Van Lommel <brechtvanlommel@pandora.be>
Sat, 3 Nov 2012 14:32:35 +0000 (14:32 +0000)
Documentation here:
http://wiki.blender.org/index.php/Doc:2.6/Manual/Render/Cycles/Nodes/OSL
http://wiki.blender.org/index.php/Dev:Ref/Release_Notes/2.65/Cycles

These changes require an OSL build from this repository:
https://github.com/DingTo/OpenShadingLanguage

The lib/ OSL has not been updated yet, so you might want to keep OSL disabled
until that is done.

Still todo:
* Auto update for external .osl files not working currently, press update manually
* Node could indicate better when a refresh is needed
* Attributes like UV or generated coordinates may be missing when requested from
  an OSL shader, need a way to request them to be loaded by cycles
* Expose string, enum and other non-socket parameters
* Scons build support

Thanks to Thomas, Lukas and Dalai for the implementation.

12 files changed:
intern/cycles/blender/CMakeLists.txt
intern/cycles/blender/addon/__init__.py
intern/cycles/blender/addon/osl.py [new file with mode: 0644]
intern/cycles/blender/blender_python.cpp
intern/cycles/blender/blender_shader.cpp
intern/cycles/render/nodes.cpp
intern/cycles/render/nodes.h
intern/cycles/render/osl.cpp
intern/cycles/render/osl.h
intern/cycles/render/shader.h
intern/cycles/util/util_path.cpp
intern/cycles/util/util_path.h

index a8c7eef..96948e4 100644 (file)
@@ -38,6 +38,7 @@ set(ADDON_FILES
        addon/__init__.py
        addon/engine.py 
        addon/enums.py
+       addon/osl.py
        addon/presets.py
        addon/properties.py
        addon/ui.py
index 6292c09..16697c0 100644 (file)
@@ -71,6 +71,13 @@ class CyclesRender(bpy.types.RenderEngine):
     def view_draw(self, context):
         engine.draw(self, context.region, context.space_data, context.region_data)
 
+    def update_script_node(self, node):
+        if engine.with_osl():
+            from . import osl
+            osl.update_script_node(node, self.report)
+        else:
+            self.report({'ERROR'}, "OSL support disabled in this build.")
+
 
 def register():
     properties.register()
@@ -84,3 +91,4 @@ def unregister():
     properties.unregister()
     presets.unregister()
     bpy.utils.unregister_module(__name__)
+
diff --git a/intern/cycles/blender/addon/osl.py b/intern/cycles/blender/addon/osl.py
new file mode 100644 (file)
index 0000000..6b46fef
--- /dev/null
@@ -0,0 +1,124 @@
+#
+# Copyright 2011, Blender Foundation.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+
+# <pep8 compliant>
+
+import bpy, _cycles, os, tempfile
+
+# compile .osl file with given filepath to temporary .oso file
+def osl_compile(input_path, report):
+    output_file = tempfile.NamedTemporaryFile(mode='w', suffix=".oso", delete=False)
+    output_path = output_file.name
+    output_file.close()
+
+    ok = _cycles.osl_compile(input_path, output_path)
+
+    if ok:
+        report({'INFO'}, "OSL shader compilation succeeded")
+
+    return ok, output_path
+
+# compile and update shader script node
+def update_script_node(node, report):
+    import os, shutil
+
+    if node.mode == 'EXTERNAL':
+        # compile external script file
+        script_path = bpy.path.abspath(node.filepath)
+        script_path_noext, script_ext = os.path.splitext(script_path)
+
+        if script_ext == ".oso":
+            # it's a .oso file, no need to compile
+            ok, oso_path = True, script_path
+            oso_file_remove = False
+        elif script_ext == ".osl":
+            # compile .osl file
+            ok, oso_path = osl_compile(script_path, report)
+            oso_file_remove = True
+
+            if ok:
+                # copy .oso from temporary path to .osl directory
+                dst_path = script_path_noext + ".oso"
+                try:
+                    shutil.copy2(oso_path, dst_path)
+                except:
+                    report({'ERROR'}, "Failed to write .oso file next to external .osl file at " + dst_path)
+        elif os.path.dirname(node.filepath) == "":
+            # module in search path
+            oso_path = node.filepath
+            oso_file_remove = False
+            ok = True
+        else:
+            # unknown
+            report({'ERROR'}, "External shader script must have .osl or .oso extension, or be a module name")
+            ok = False
+
+        if ok:
+            node.bytecode = ""
+            node.bytecode_hash = ""
+
+    elif node.mode == 'INTERNAL' and node.script:
+        # internal script, we will store bytecode in the node
+        script = node.script
+        osl_path = bpy.path.abspath(script.filepath)
+
+        if script.is_in_memory or script.is_dirty or script.is_modified or not os.path.exists(osl_path):
+            # write text datablock contents to temporary file
+            osl_file = tempfile.NamedTemporaryFile(mode='w', suffix=".osl", delete=True)
+            osl_file.write(script.as_string())
+            osl_file.flush()
+            ok, oso_path = osl_compile(osl_file.name, report)
+            oso_file_remove = False
+            osl_file.close()
+        else:
+            # compile text datablock from disk directly
+            ok, oso_path = osl_compile(osl_path, report)
+            oso_file_remove = False
+
+        if ok:
+            # read bytecode
+            try:
+                oso = open(oso_path, 'r')
+                node.bytecode = oso.read()
+                oso.close()
+            except:
+                report({'ERROR'}, "Can't read OSO bytecode to store in node at " + oso_path)
+                ok = False
+    
+    else:
+        report({'WARNING'}, "No text or file specified in node, nothing to compile")
+        return
+
+    if ok:
+        # now update node with new sockets
+        ok = _cycles.osl_update_node(node.id_data.as_pointer(), node.as_pointer(), oso_path)
+
+        if not ok:
+            report({'ERROR'}, "OSL query failed to open " + oso_path)
+    else:
+        report({'ERROR'}, "OSL script compilation failed, see console for errors")
+
+    # remove temporary oso file
+    if oso_file_remove:
+        try:
+            os.remove(oso_path)
+        except:
+            pass
+
+    return ok
+
index d9220b7..c047805 100644 (file)
 #include "blender_session.h"
 
 #include "util_foreach.h"
+#include "util_md5.h"
 #include "util_opengl.h"
 #include "util_path.h"
 
+#ifdef WITH_OSL
+#include "osl.h"
+
+#include <OSL/oslquery.h>
+#include <OSL/oslconfig.h>
+#endif
+
 CCL_NAMESPACE_BEGIN
 
 static PyObject *init_func(PyObject *self, PyObject *args)
@@ -163,6 +171,170 @@ static PyObject *available_devices_func(PyObject *self, PyObject *args)
        return ret;
 }
 
+#ifdef WITH_OSL
+static PyObject *osl_update_node_func(PyObject *self, PyObject *args)
+{
+       PyObject *pynodegroup, *pynode;
+       const char *filepath = NULL;
+
+       if(!PyArg_ParseTuple(args, "OOs", &pynodegroup, &pynode, &filepath))
+               return NULL;
+
+       /* RNA */
+       PointerRNA nodeptr;
+       RNA_pointer_create((ID*)PyLong_AsVoidPtr(pynodegroup), &RNA_ShaderNodeScript, (void*)PyLong_AsVoidPtr(pynode), &nodeptr);
+       BL::ShaderNodeScript b_node(nodeptr);
+
+       /* update bytecode hash */
+       string bytecode = b_node.bytecode();
+
+       if(!bytecode.empty()) {
+               MD5Hash md5;
+               md5.append((const uint8_t*)bytecode.c_str(), bytecode.size());
+               b_node.bytecode_hash(md5.get_hex().c_str());
+       }
+       else
+               b_node.bytecode_hash("");
+
+       /* query from file path */
+       OSL::OSLQuery query;
+
+       if(!OSLShaderManager::osl_query(query, filepath))
+               Py_RETURN_FALSE;
+
+       /* add new sockets from parameters */
+       set<void*> used_sockets;
+
+       for(int i = 0; i < query.nparams(); i++) {
+               const OSL::OSLQuery::Parameter *param = query.getparam(i);
+
+               /* skip unsupported types */
+               if(param->varlenarray || param->isstruct || param->type.arraylen > 1)
+                       continue;
+
+               /* determine socket type */
+               BL::NodeSocket::type_enum socket_type;
+               float default_float4[4] = {0.0f, 0.0f, 0.0f, 1.0f};
+               float default_float = 0.0f;
+               int default_int = 0;
+               
+               if(param->isclosure) {
+                       socket_type = BL::NodeSocket::type_SHADER;
+               }
+               else if(param->type.vecsemantics == TypeDesc::COLOR) {
+                       socket_type = BL::NodeSocket::type_RGBA;
+
+                       if(param->validdefault) {
+                               default_float4[0] = param->fdefault[0];
+                               default_float4[1] = param->fdefault[1];
+                               default_float4[2] = param->fdefault[2];
+                       }
+               }
+               else if(param->type.vecsemantics == TypeDesc::POINT ||
+                       param->type.vecsemantics == TypeDesc::VECTOR ||
+                       param->type.vecsemantics == TypeDesc::NORMAL) {
+                       socket_type = BL::NodeSocket::type_VECTOR;
+
+                       if(param->validdefault) {
+                               default_float4[0] = param->fdefault[0];
+                               default_float4[1] = param->fdefault[1];
+                               default_float4[2] = param->fdefault[2];
+                       }
+               }
+               else if(param->type.aggregate == TypeDesc::SCALAR) {
+                       if(param->type.basetype == TypeDesc::INT) {
+                               socket_type = BL::NodeSocket::type_INT;
+                               if(param->validdefault)
+                                       default_int = param->idefault[0];
+                       }
+                       else if(param->type.basetype == TypeDesc::FLOAT) {
+                               socket_type = BL::NodeSocket::type_VALUE;
+                               if(param->validdefault)
+                                       default_float = param->fdefault[0];
+                       }
+               }
+               else
+                       continue;
+
+               /* find socket socket */
+               BL::NodeSocket b_sock = b_node.find_socket(param->name.c_str(), param->isoutput);
+
+               /* remove if type no longer matches */
+               if(b_sock && b_sock.type() != socket_type) {
+                       b_node.remove_socket(b_sock);
+                       b_sock = BL::NodeSocket(PointerRNA_NULL);
+               }
+
+               /* create new socket */
+               if(!b_sock) {
+                       b_sock = b_node.add_socket(param->name.c_str(), socket_type, param->isoutput);
+
+                       /* set default value */
+                       if(socket_type == BL::NodeSocket::type_VALUE) {
+                               BL::NodeSocketFloatNone b_float_sock(b_sock.ptr);
+                               b_float_sock.default_value(default_float);
+                       }
+                       else if(socket_type == BL::NodeSocket::type_INT) {
+                               BL::NodeSocketIntNone b_int_sock(b_sock.ptr);
+                               b_int_sock.default_value(default_int);
+                       }
+                       else if(socket_type == BL::NodeSocket::type_RGBA) {
+                               BL::NodeSocketRGBA b_rgba_sock(b_sock.ptr);
+                               b_rgba_sock.default_value(default_float4);
+                       }
+                       else if(socket_type == BL::NodeSocket::type_VECTOR) {
+                               BL::NodeSocketVectorNone b_vector_sock(b_sock.ptr);
+                               b_vector_sock.default_value(default_float4);
+                       }
+               }
+
+               used_sockets.insert(b_sock.ptr.data);
+       }
+
+       /* remove unused parameters */
+       bool removed;
+
+       do {
+               BL::Node::inputs_iterator b_input;
+               BL::Node::outputs_iterator b_output;
+
+               removed = false;
+
+               for (b_node.inputs.begin(b_input); b_input != b_node.inputs.end(); ++b_input) {
+                       if(used_sockets.find(b_input->ptr.data) == used_sockets.end()) {
+                               b_node.remove_socket(*b_input);
+                               removed = true;
+                               break;
+                       }
+               }
+
+               for (b_node.outputs.begin(b_output); b_output != b_node.outputs.end(); ++b_output) {
+                       if(used_sockets.find(b_output->ptr.data) == used_sockets.end()) {
+                               b_node.remove_socket(*b_output);
+                               removed = true;
+                               break;
+                       }
+               }
+       } while(removed);
+
+       Py_RETURN_TRUE;
+}
+
+static PyObject *osl_compile_func(PyObject *self, PyObject *args)
+{
+       const char *inputfile = NULL, *outputfile = NULL;
+
+       if(!PyArg_ParseTuple(args, "ss", &inputfile, &outputfile))
+               return NULL;
+       
+       /* return */
+       if(!OSLShaderManager::osl_compile(inputfile, outputfile))
+               Py_RETURN_FALSE;
+
+       Py_RETURN_TRUE;
+}
+#endif
+
 static PyMethodDef methods[] = {
        {"init", init_func, METH_VARARGS, ""},
        {"create", create_func, METH_VARARGS, ""},
@@ -170,6 +342,10 @@ static PyMethodDef methods[] = {
        {"render", render_func, METH_O, ""},
        {"draw", draw_func, METH_VARARGS, ""},
        {"sync", sync_func, METH_O, ""},
+#ifdef WITH_OSL
+       {"osl_update_node", osl_update_node_func, METH_VARARGS, ""},
+       {"osl_compile", osl_compile_func, METH_VARARGS, ""},
+#endif
        {"available_devices", available_devices_func, METH_NOARGS, ""},
        {NULL, NULL, 0, NULL},
 };
index 188996c..9e3380c 100644 (file)
@@ -20,6 +20,7 @@
 #include "graph.h"
 #include "light.h"
 #include "nodes.h"
+#include "osl.h"
 #include "scene.h"
 #include "shader.h"
 
@@ -159,7 +160,7 @@ static void get_tex_mapping(TextureMapping *mapping, BL::ShaderNodeMapping b_map
                mapping->max = get_float3(b_mapping.max());
 }
 
-static ShaderNode *add_node(BL::BlendData b_data, BL::Scene b_scene, ShaderGraph *graph, BL::ShaderNode b_node)
+static ShaderNode *add_node(Scene *scene, BL::BlendData b_data, BL::Scene b_scene, ShaderGraph *graph, BL::ShaderNodeTree b_ntree, BL::ShaderNode b_node)
 {
        ShaderNode *node = NULL;
 
@@ -413,6 +414,58 @@ static ShaderNode *add_node(BL::BlendData b_data, BL::Scene b_scene, ShaderGraph
                        node = new BumpNode();
                        break;
                }
+               case BL::ShaderNode::type_SCRIPT: {
+#ifdef WITH_OSL
+                       if(scene->params.shadingsystem != SceneParams::OSL)
+                               break;
+
+                       /* create script node */
+                       BL::ShaderNodeScript b_script_node(b_node);
+                       OSLScriptNode *script_node = new OSLScriptNode();
+                       
+                       /* Generate inputs/outputs from node sockets
+                        *
+                        * Note: the node sockets are generated from OSL parameters,
+                        * so the names match those of the corresponding parameters exactly.
+                        *
+                        * Note 2: ShaderInput/ShaderOutput store shallow string copies only!
+                        * Socket names must be stored in the extra lists instead. */
+                       BL::Node::inputs_iterator b_input;
+
+                       for (b_script_node.inputs.begin(b_input); b_input != b_script_node.inputs.end(); ++b_input) {
+                               script_node->input_names.push_back(ustring(b_input->name()));
+                               ShaderInput *input = script_node->add_input(script_node->input_names.back().c_str(), convert_socket_type(b_input->type()));
+                               set_default_value(input, *b_input);
+                       }
+
+                       BL::Node::outputs_iterator b_output;
+
+                       for (b_script_node.outputs.begin(b_output); b_output != b_script_node.outputs.end(); ++b_output) {
+                               script_node->output_names.push_back(ustring(b_output->name()));
+                               script_node->add_output(script_node->output_names.back().c_str(), convert_socket_type(b_output->type()));
+                       }
+
+                       /* load bytecode or filepath */
+                       OSLShaderManager *manager = (OSLShaderManager*)scene->shader_manager;
+                       string bytecode_hash = b_script_node.bytecode_hash();
+
+                       if(!bytecode_hash.empty()) {
+                               /* loaded bytecode if not already done */
+                               if(!manager->shader_test_loaded(bytecode_hash))
+                                       manager->shader_load_bytecode(bytecode_hash, b_script_node.bytecode());
+
+                               script_node->bytecode_hash = bytecode_hash;
+                       }
+                       else {
+                               /* set filepath */
+                               script_node->filepath = blender_absolute_path(b_data, b_ntree, b_script_node.filepath());
+                       }
+                       
+                       node = script_node;
+#endif
+
+                       break;
+               }
                case BL::ShaderNode::type_TEX_IMAGE: {
                        BL::ShaderNodeTexImage b_image_node(b_node);
                        BL::Image b_image(b_image_node.image());
@@ -576,7 +629,7 @@ static SocketPair node_socket_map_pair(PtrNodeMap& node_map, BL::Node b_node, BL
        return SocketPair(node_map[b_node.ptr.data], name);
 }
 
-static void add_nodes(BL::BlendData b_data, BL::Scene b_scene, ShaderGraph *graph, BL::ShaderNodeTree b_ntree, PtrSockMap& sockets_map)
+static void add_nodes(Scene *scene, BL::BlendData b_data, BL::Scene b_scene, ShaderGraph *graph, BL::ShaderNodeTree b_ntree, PtrSockMap& sockets_map)
 {
        /* add nodes */
        BL::ShaderNodeTree::nodes_iterator b_node;
@@ -651,10 +704,10 @@ static void add_nodes(BL::BlendData b_data, BL::Scene b_scene, ShaderGraph *grap
                                set_default_value(proxy->inputs[0], b_output->group_socket());
                        }
                        
-                       add_nodes(b_data, b_scene, graph, b_group_ntree, group_sockmap);
+                       add_nodes(scene, b_data, b_scene, graph, b_group_ntree, group_sockmap);
                }
                else {
-                       ShaderNode *node = add_node(b_data, b_scene, graph, BL::ShaderNode(*b_node));
+                       ShaderNode *node = add_node(scene, b_data, b_scene, graph, b_ntree, BL::ShaderNode(*b_node));
                        
                        if(node) {
                                BL::Node::inputs_iterator b_input;
@@ -744,7 +797,7 @@ void BlenderSync::sync_materials()
                                PtrSockMap sock_to_node;
                                BL::ShaderNodeTree b_ntree(b_mat->node_tree());
 
-                               add_nodes(b_data, b_scene, graph, b_ntree, sock_to_node);
+                               add_nodes(scene, b_data, b_scene, graph, b_ntree, sock_to_node);
                        }
                        else {
                                ShaderNode *closure, *out;
@@ -785,7 +838,7 @@ void BlenderSync::sync_world()
                        PtrSockMap sock_to_node;
                        BL::ShaderNodeTree b_ntree(b_world.node_tree());
 
-                       add_nodes(b_data, b_scene, graph, b_ntree, sock_to_node);
+                       add_nodes(scene, b_data, b_scene, graph, b_ntree, sock_to_node);
                }
                else if(b_world) {
                        ShaderNode *closure, *out;
@@ -844,7 +897,7 @@ void BlenderSync::sync_lamps()
                                PtrSockMap sock_to_node;
                                BL::ShaderNodeTree b_ntree(b_lamp->node_tree());
 
-                               add_nodes(b_data, b_scene, graph, b_ntree, sock_to_node);
+                               add_nodes(scene, b_data, b_scene, graph, b_ntree, sock_to_node);
                        }
                        else {
                                ShaderNode *closure, *out;
index 2cdab3a..42ab3fe 100644 (file)
@@ -2922,5 +2922,25 @@ void SetNormalNode::compile(OSLCompiler& compiler)
        compiler.add(this, "node_set_normal"); 
 }
 
+/* OSLScriptNode */
+
+OSLScriptNode::OSLScriptNode()
+: ShaderNode("osl_script")
+{
+}
+
+void OSLScriptNode::compile(SVMCompiler& compiler)
+{
+       /* doesn't work for SVM, obviously ... */
+}
+
+void OSLScriptNode::compile(OSLCompiler& compiler)
+{
+       if(!filepath.empty())
+               compiler.add(this, filepath.c_str(), true);
+       else
+               compiler.add(this, bytecode_hash.c_str(), false);
+}
+
 CCL_NAMESPACE_END
 
index fbc61e1..0508bf6 100644 (file)
@@ -445,6 +445,18 @@ public:
        SHADER_NODE_CLASS(SetNormalNode)
 };
 
+class OSLScriptNode : public ShaderNode {
+public:
+       SHADER_NODE_CLASS(OSLScriptNode)
+       string filepath;
+       string bytecode_hash;
+       
+       /* ShaderInput/ShaderOutput only stores a shallow string copy (const char *)!
+        * The actual socket names have to be stored externally to avoid memory errors. */
+       vector<ustring> input_names;
+       vector<ustring> output_names;
+};
+
 CCL_NAMESPACE_END
 
 #endif /* __NODES_H__ */
index 4856a8d..a8a40a4 100644 (file)
@@ -31,6 +31,7 @@
 #include "osl_shader.h"
 
 #include "util_foreach.h"
+#include "util_md5.h"
 #include "util_path.h"
 #include "util_progress.h"
 
@@ -46,36 +47,8 @@ OSLShaderManager::OSLShaderManager()
 {
        services = new OSLRenderServices();
 
-       /* if we let OSL create it, it leaks */
-       ts = TextureSystem::create(true);
-       ts->attribute("automip",  1);
-       ts->attribute("autotile", 64);
-
-       ss = OSL::ShadingSystem::create(services, ts, &errhandler);
-       ss->attribute("lockgeom", 1);
-       ss->attribute("commonspace", "world");
-       ss->attribute("optimize", 2);
-       //ss->attribute("debug", 1);
-       //ss->attribute("statistics:level", 1);
-       ss->attribute("searchpath:shader", path_get("shader").c_str());
-
-       /* our own ray types */
-       static const char *raytypes[] = {
-               "camera",               /* PATH_RAY_CAMERA */
-               "reflection",   /* PATH_RAY_REFLECT */
-               "refraction",   /* PATH_RAY_TRANSMIT */
-               "diffuse",              /* PATH_RAY_DIFFUSE */
-               "glossy",               /* PATH_RAY_GLOSSY */
-               "singular",             /* PATH_RAY_SINGULAR */
-               "transparent",  /* PATH_RAY_TRANSPARENT */
-               "shadow",               /* PATH_RAY_SHADOW_OPAQUE */
-               "shadow",               /* PATH_RAY_SHADOW_TRANSPARENT */
-       };
-
-       const int nraytypes = sizeof(raytypes)/sizeof(raytypes[0]);
-       ss->attribute("raytypes", TypeDesc(TypeDesc::STRING, nraytypes), raytypes);
-
-       OSLShader::register_closures(ss);
+       shading_system_init();
+       texture_system_init();
 }
 
 OSLShaderManager::~OSLShaderManager()
@@ -87,13 +60,6 @@ OSLShaderManager::~OSLShaderManager()
 
 void OSLShaderManager::device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress& progress)
 {
-       /* test if we need to update */
-       bool need_update = false;
-
-       foreach(Shader *shader, scene->shaders)
-               if(shader->need_update)
-                       need_update = true;
-       
        if(!need_update)
                return;
 
@@ -113,7 +79,7 @@ void OSLShaderManager::device_update(Device *device, DeviceScene *dscene, Scene
                if(shader->sample_as_light && shader->has_surface_emission)
                        scene->light_manager->need_update = true;
 
-               OSLCompiler compiler((void*)ss);
+               OSLCompiler compiler((void*)this, (void*)ss);
                compiler.background = (shader == scene->shaders[scene->default_background]);
                compiler.compile(og, shader);
        }
@@ -129,6 +95,8 @@ void OSLShaderManager::device_update(Device *device, DeviceScene *dscene, Scene
 
        foreach(Shader *shader, scene->shaders)
                shader->need_update = false;
+
+       need_update = false;
        
        /* set texture system */
        scene->image_manager->set_osl_texture_system((void*)ts);
@@ -154,10 +122,165 @@ void OSLShaderManager::device_free(Device *device, DeviceScene *dscene)
        og->background_state.reset();
 }
 
+void OSLShaderManager::texture_system_init()
+{
+       /* if we let OSL create it, it leaks */
+       ts = TextureSystem::create(true);
+       ts->attribute("automip",  1);
+       ts->attribute("autotile", 64);
+
+       /* effectively unlimited for now, until we support proper mipmap lookups */
+       ts->attribute("max_memory_MB", 16384);
+}
+
+void OSLShaderManager::shading_system_init()
+{
+       ss = OSL::ShadingSystem::create(services, ts, &errhandler);
+       ss->attribute("lockgeom", 1);
+       ss->attribute("commonspace", "world");
+       ss->attribute("optimize", 2);
+       //ss->attribute("debug", 1);
+       //ss->attribute("statistics:level", 1);
+       ss->attribute("searchpath:shader", path_get("shader"));
+
+       /* our own ray types */
+       static const char *raytypes[] = {
+               "camera",               /* PATH_RAY_CAMERA */
+               "reflection",   /* PATH_RAY_REFLECT */
+               "refraction",   /* PATH_RAY_TRANSMIT */
+               "diffuse",              /* PATH_RAY_DIFFUSE */
+               "glossy",               /* PATH_RAY_GLOSSY */
+               "singular",             /* PATH_RAY_SINGULAR */
+               "transparent",  /* PATH_RAY_TRANSPARENT */
+               "shadow",               /* PATH_RAY_SHADOW_OPAQUE */
+               "shadow",               /* PATH_RAY_SHADOW_TRANSPARENT */
+       };
+
+       const int nraytypes = sizeof(raytypes)/sizeof(raytypes[0]);
+       ss->attribute("raytypes", TypeDesc(TypeDesc::STRING, nraytypes), raytypes);
+
+       OSLShader::register_closures(ss);
+
+       loaded_shaders.clear();
+}
+
+bool OSLShaderManager::osl_compile(const string& inputfile, const string& outputfile)
+{
+       vector<string> options;
+       string stdosl_path;
+
+       /* specify output file name */
+       options.push_back("-o");
+       options.push_back(outputfile);
+
+       /* specify standard include path */
+       options.push_back("-I" + path_get("shader"));
+       stdosl_path = path_get("shader/stdosl.h");
+
+       /* compile */
+       OSL::OSLCompiler *compiler = OSL::OSLCompiler::create();
+       bool ok = compiler->compile(inputfile, options, stdosl_path);
+       delete compiler;
+
+       return ok;
+}
+
+bool OSLShaderManager::osl_query(OSL::OSLQuery& query, const string& filepath)
+{
+       string searchpath = path_user_get("shaders");
+       return query.open(filepath, searchpath);
+}
+
+static string shader_filepath_hash(const string& filepath, uint64_t modified_time)
+{
+       /* compute a hash from filepath and modified time to detect changes */
+       MD5Hash md5;
+       md5.append((const uint8_t*)filepath.c_str(), filepath.size());
+       md5.append((const uint8_t*)&modified_time, sizeof(modified_time));
+
+       return md5.get_hex();
+}
+
+const char *OSLShaderManager::shader_test_loaded(const string& hash)
+{
+       set<string>::iterator it = loaded_shaders.find(hash);
+       return (it == loaded_shaders.end())? NULL: it->c_str();
+}
+
+const char *OSLShaderManager::shader_load_filepath(string filepath)
+{
+       size_t len = filepath.size();
+       string extension = filepath.substr(len - 4);
+       uint64_t modified_time = path_modified_time(filepath);
+
+       if(extension == ".osl") {
+               /* .OSL File */
+               string osopath = filepath.substr(0, len - 4) + ".oso";
+               uint64_t oso_modified_time = path_modified_time(osopath);
+
+               /* test if we have loaded the corresponding .OSO already */
+               if(oso_modified_time != 0) {
+                       const char *hash = shader_test_loaded(shader_filepath_hash(osopath, oso_modified_time));
+
+                       if(hash)
+                               return hash;
+               }
+
+               /* autocompile .OSL to .OSO if needed */
+               if(oso_modified_time == 0 || (oso_modified_time < modified_time)) {
+                       OSLShaderManager::osl_compile(filepath, osopath);
+                       modified_time = path_modified_time(osopath);
+               }
+               else
+                       modified_time = oso_modified_time;
+
+               filepath = osopath;
+       }
+       else {
+               if(extension == ".oso") {
+                       /* .OSO File, nothing to do */
+               }
+               else if(path_dirname(filepath) == "") {
+                       /* .OSO File in search path */
+                       filepath = path_join(path_user_get("shaders"), filepath + ".oso");
+               }
+               else {
+                       /* unknown file */
+                       return NULL;
+               }
+
+               /* test if we have loaded this .OSO already */
+               const char *hash = shader_test_loaded(shader_filepath_hash(filepath, modified_time));
+
+               if(hash)
+                       return hash;
+       }
+
+       /* read oso bytecode from file */
+       string bytecode_hash = shader_filepath_hash(filepath, modified_time);
+       string bytecode;
+
+       if(!path_read_text(filepath, bytecode)) {
+               fprintf(stderr, "Cycles shader graph: failed to read file %s\n", filepath.c_str());
+               loaded_shaders.insert(bytecode_hash); /* to avoid repeat tries */
+               return NULL;
+       }
+
+       return shader_load_bytecode(bytecode_hash, bytecode);
+}
+
+const char *OSLShaderManager::shader_load_bytecode(const string& hash, const string& bytecode)
+{
+       ss->LoadMemoryShader(hash.c_str(), bytecode.c_str());
+
+       return loaded_shaders.insert(hash).first->c_str();
+}
+
 /* Graph Compiler */
 
-OSLCompiler::OSLCompiler(void *shadingsys_)
+OSLCompiler::OSLCompiler(void *manager_, void *shadingsys_)
 {
+       manager = manager_;
        shadingsys = shadingsys_;
        current_type = SHADER_TYPE_SURFACE;
        current_shader = NULL;
@@ -238,10 +361,18 @@ bool OSLCompiler::node_skip_input(ShaderNode *node, ShaderInput *input)
        return false;
 }
 
-void OSLCompiler::add(ShaderNode *node, const char *name)
+void OSLCompiler::add(ShaderNode *node, const char *name, bool isfilepath)
 {
        OSL::ShadingSystem *ss = (OSL::ShadingSystem*)shadingsys;
 
+       /* load filepath */
+       if(isfilepath) {
+               name = ((OSLShaderManager*)manager)->shader_load_filepath(name);
+
+               if(name == NULL)
+                       return;
+       }
+
        /* pass in fixed parameter values */
        foreach(ShaderInput *input, node->inputs) {
                if(!input->link) {
@@ -510,82 +641,85 @@ void OSLCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty
 
 void OSLCompiler::compile(OSLGlobals *og, Shader *shader)
 {
-       OSL::ShadingSystem *ss = (OSL::ShadingSystem*)shadingsys;
-       ShaderGraph *graph = shader->graph;
-       ShaderNode *output = (graph)? graph->output(): NULL;
+       if(shader->need_update) {
+               OSL::ShadingSystem *ss = (OSL::ShadingSystem*)shadingsys;
+               ShaderGraph *graph = shader->graph;
+               ShaderNode *output = (graph)? graph->output(): NULL;
 
-       /* copy graph for shader with bump mapping */
-       if(output->input("Surface")->link && output->input("Displacement")->link)
-               if(!shader->graph_bump)
-                       shader->graph_bump = shader->graph->copy();
+               /* copy graph for shader with bump mapping */
+               if(output->input("Surface")->link && output->input("Displacement")->link)
+                       if(!shader->graph_bump)
+                               shader->graph_bump = shader->graph->copy();
 
-       /* finalize */
-       shader->graph->finalize(false, true);
-       if(shader->graph_bump)
-               shader->graph_bump->finalize(true, true);
+               /* finalize */
+               shader->graph->finalize(false, true);
+               if(shader->graph_bump)
+                       shader->graph_bump->finalize(true, true);
 
-       current_shader = shader;
+               current_shader = shader;
 
-       shader->has_surface = false;
-       shader->has_surface_emission = false;
-       shader->has_surface_transparent = false;
-       shader->has_volume = false;
-       shader->has_displacement = false;
+               shader->has_surface = false;
+               shader->has_surface_emission = false;
+               shader->has_surface_transparent = false;
+               shader->has_volume = false;
+               shader->has_displacement = false;
 
-       /* generate surface shader */
-       if(shader->used && graph && output->input("Surface")->link) {
-               compile_type(shader, shader->graph, SHADER_TYPE_SURFACE);
-               og->surface_state.push_back(ss->state());
+               /* generate surface shader */
+               if(shader->used && graph && output->input("Surface")->link) {
+                       compile_type(shader, shader->graph, SHADER_TYPE_SURFACE);
+                       shader->osl_surface_ref = ss->state();
+
+                       if(shader->graph_bump) {
+                               ss->clear_state();
+                               compile_type(shader, shader->graph_bump, SHADER_TYPE_SURFACE);
+                       }
 
-               if(shader->graph_bump) {
+                       shader->osl_surface_bump_ref = ss->state();
                        ss->clear_state();
-                       compile_type(shader, shader->graph_bump, SHADER_TYPE_SURFACE);
-                       og->surface_state.push_back(ss->state());
-               }
-               else
-                       og->surface_state.push_back(ss->state());
 
-               ss->clear_state();
+                       shader->has_surface = true;
+               }
+               else {
+                       shader->osl_surface_ref = OSL::ShadingAttribStateRef();
+                       shader->osl_surface_bump_ref = OSL::ShadingAttribStateRef();
+               }
 
-               shader->has_surface = true;
-       }
-       else {
-               og->surface_state.push_back(OSL::ShadingAttribStateRef());
-               og->surface_state.push_back(OSL::ShadingAttribStateRef());
-       }
+               /* generate volume shader */
+               if(shader->used && graph && output->input("Volume")->link) {
+                       compile_type(shader, shader->graph, SHADER_TYPE_VOLUME);
+                       shader->has_volume = true;
 
-       /* generate volume shader */
-       if(shader->used && graph && output->input("Volume")->link) {
-               compile_type(shader, shader->graph, SHADER_TYPE_VOLUME);
-               shader->has_volume = true;
+                       shader->osl_volume_ref = ss->state();
+                       ss->clear_state();
+               }
+               else
+                       shader->osl_volume_ref = OSL::ShadingAttribStateRef();
 
-               og->volume_state.push_back(ss->state());
-               og->volume_state.push_back(ss->state());
-               ss->clear_state();
-       }
-       else {
-               og->volume_state.push_back(OSL::ShadingAttribStateRef());
-               og->volume_state.push_back(OSL::ShadingAttribStateRef());
+               /* generate displacement shader */
+               if(shader->used && graph && output->input("Displacement")->link) {
+                       compile_type(shader, shader->graph, SHADER_TYPE_DISPLACEMENT);
+                       shader->has_displacement = true;
+                       shader->osl_displacement_ref = ss->state();
+                       ss->clear_state();
+               }
+               else
+                       shader->osl_displacement_ref = OSL::ShadingAttribStateRef();
        }
 
-       /* generate displacement shader */
-       if(shader->used && graph && output->input("Displacement")->link) {
-               compile_type(shader, shader->graph, SHADER_TYPE_DISPLACEMENT);
-               shader->has_displacement = true;
+       /* push state to array for lookup */
+       og->surface_state.push_back(shader->osl_surface_ref);
+       og->surface_state.push_back(shader->osl_surface_bump_ref);
 
-               og->displacement_state.push_back(ss->state());
-               og->displacement_state.push_back(ss->state());
-               ss->clear_state();
-       }
-       else {
-               og->displacement_state.push_back(OSL::ShadingAttribStateRef());
-               og->displacement_state.push_back(OSL::ShadingAttribStateRef());
-       }
+       og->volume_state.push_back(shader->osl_volume_ref);
+       og->volume_state.push_back(shader->osl_volume_ref);
+
+       og->displacement_state.push_back(shader->osl_displacement_ref);
+       og->displacement_state.push_back(shader->osl_displacement_ref);
 }
 
 #else
 
-void OSLCompiler::add(ShaderNode *node, const char *name)
+void OSLCompiler::add(ShaderNode *node, const char *name, bool isfilepath)
 {
 }
 
index 90107a3..cee37c5 100644 (file)
 #define __OSL_H__
 
 #include "util_set.h"
+#include "util_string.h"
 
 #include "shader.h"
 
 #ifdef WITH_OSL
+#include <OSL/oslcomp.h>
 #include <OSL/oslexec.h>
+#include <OSL/oslquery.h>
 #endif
 
 CCL_NAMESPACE_BEGIN
@@ -52,11 +55,24 @@ public:
        void device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress& progress);
        void device_free(Device *device, DeviceScene *dscene);
 
-private:
+       /* osl compile and query */
+       static bool osl_compile(const string& inputfile, const string& outputfile);
+       static bool osl_query(OSL::OSLQuery& query, const string& filepath);
+
+       /* shader file loading, all functions return pointer to hash string if found */
+       const char *shader_test_loaded(const string& hash);
+       const char *shader_load_bytecode(const string& hash, const string& bytecode);
+       const char *shader_load_filepath(string filepath);
+
+protected:
+       void texture_system_init();
+       void shading_system_init();
+
        OSL::ShadingSystem *ss;
        OSL::TextureSystem *ts;
        OSLRenderServices *services;
        OSL::ErrorHandler errhandler;
+       set<string> loaded_shaders;
 };
 
 #endif
@@ -65,10 +81,10 @@ private:
 
 class OSLCompiler {
 public:
-       OSLCompiler(void *shadingsys);
+       OSLCompiler(void *manager, void *shadingsys);
        void compile(OSLGlobals *og, Shader *shader);
 
-       void add(ShaderNode *node, const char *name);
+       void add(ShaderNode *node, const char *name, bool isfilepath = false);
 
        void parameter(const char *name, float f);
        void parameter_color(const char *name, float3 f);
@@ -104,6 +120,7 @@ private:
        void generate_nodes(const set<ShaderNode*>& nodes);
 
        void *shadingsys;
+       void *manager;
        ShaderType current_type;
        Shader *current_shader;
 };
index 90ae67e..373b335 100644 (file)
 #include "util_string.h"
 #include "util_types.h"
 
+#ifdef WITH_OSL
+#include <OSL/oslexec.h>
+#endif
+
 CCL_NAMESPACE_BEGIN
 
 class Device;
@@ -78,6 +82,14 @@ public:
        /* determined before compiling */
        bool used;
 
+#ifdef WITH_OSL
+       /* osl shading state references */
+       OSL::ShadingAttribStateRef osl_surface_ref;
+       OSL::ShadingAttribStateRef osl_surface_bump_ref;
+       OSL::ShadingAttribStateRef osl_volume_ref;
+       OSL::ShadingAttribStateRef osl_displacement_ref;
+#endif
+
        Shader();
        ~Shader();
 
index a571fe8..8cf23bc 100644 (file)
@@ -170,7 +170,7 @@ bool path_read_binary(const string& path, vector<uint8_t>& binary)
        return true;
 }
 
-static bool path_read_text(const string& path, string& text)
+bool path_read_text(const string& path, string& text)
 {
        vector<uint8_t> binary;
 
@@ -184,6 +184,14 @@ static bool path_read_text(const string& path, string& text)
        return true;
 }
 
+uint64_t path_modified_time(const string& path)
+{
+       if(boost::filesystem::exists(path))
+               return (uint64_t)boost::filesystem::last_write_time(path);
+       
+       return 0;
+}
+
 string path_source_replace_includes(const string& source_, const string& path)
 {
        /* our own little c preprocessor that replaces #includes with the file
index 6cba6a3..89e4452 100644 (file)
@@ -45,6 +45,9 @@ string path_files_md5_hash(const string& dir);
 void path_create_directories(const string& path);
 bool path_write_binary(const string& path, const vector<uint8_t>& binary);
 bool path_read_binary(const string& path, vector<uint8_t>& binary);
+bool path_read_text(const string& path, string& text);
+
+uint64_t path_modified_time(const string& path);
 
 string path_source_replace_includes(const string& source, const string& path);