From eb87529e23cdc744ed52b00f3de25e208b29d7f1 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sat, 3 Nov 2012 14:32:35 +0000 Subject: [PATCH] Cycles OSL: shader script node 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. --- intern/cycles/blender/CMakeLists.txt | 1 + intern/cycles/blender/addon/__init__.py | 8 + intern/cycles/blender/addon/osl.py | 124 +++++++++ intern/cycles/blender/blender_python.cpp | 176 ++++++++++++ intern/cycles/blender/blender_shader.cpp | 67 ++++- intern/cycles/render/nodes.cpp | 20 ++ intern/cycles/render/nodes.h | 12 + intern/cycles/render/osl.cpp | 330 ++++++++++++++++------- intern/cycles/render/osl.h | 23 +- intern/cycles/render/shader.h | 12 + intern/cycles/util/util_path.cpp | 10 +- intern/cycles/util/util_path.h | 3 + 12 files changed, 677 insertions(+), 109 deletions(-) create mode 100644 intern/cycles/blender/addon/osl.py diff --git a/intern/cycles/blender/CMakeLists.txt b/intern/cycles/blender/CMakeLists.txt index a8c7eef89fa..96948e49d1a 100644 --- a/intern/cycles/blender/CMakeLists.txt +++ b/intern/cycles/blender/CMakeLists.txt @@ -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 diff --git a/intern/cycles/blender/addon/__init__.py b/intern/cycles/blender/addon/__init__.py index 6292c09fbb1..16697c08b2b 100644 --- a/intern/cycles/blender/addon/__init__.py +++ b/intern/cycles/blender/addon/__init__.py @@ -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 index 00000000000..6b46fefce92 --- /dev/null +++ b/intern/cycles/blender/addon/osl.py @@ -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. +# + +# + +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 + diff --git a/intern/cycles/blender/blender_python.cpp b/intern/cycles/blender/blender_python.cpp index d9220b76835..c047805c6ae 100644 --- a/intern/cycles/blender/blender_python.cpp +++ b/intern/cycles/blender/blender_python.cpp @@ -24,9 +24,17 @@ #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 +#include +#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 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}, }; diff --git a/intern/cycles/blender/blender_shader.cpp b/intern/cycles/blender/blender_shader.cpp index 188996cc34d..9e3380c6f8e 100644 --- a/intern/cycles/blender/blender_shader.cpp +++ b/intern/cycles/blender/blender_shader.cpp @@ -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; diff --git a/intern/cycles/render/nodes.cpp b/intern/cycles/render/nodes.cpp index 2cdab3a6560..42ab3fe17aa 100644 --- a/intern/cycles/render/nodes.cpp +++ b/intern/cycles/render/nodes.cpp @@ -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 diff --git a/intern/cycles/render/nodes.h b/intern/cycles/render/nodes.h index fbc61e12fd4..0508bf6b266 100644 --- a/intern/cycles/render/nodes.h +++ b/intern/cycles/render/nodes.h @@ -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 input_names; + vector output_names; +}; + CCL_NAMESPACE_END #endif /* __NODES_H__ */ diff --git a/intern/cycles/render/osl.cpp b/intern/cycles/render/osl.cpp index 4856a8d4e0c..a8a40a4e596 100644 --- a/intern/cycles/render/osl.cpp +++ b/intern/cycles/render/osl.cpp @@ -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 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::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) { + if(shader->graph_bump) { + ss->clear_state(); + compile_type(shader, shader->graph_bump, SHADER_TYPE_SURFACE); + } + + shader->osl_surface_bump_ref = ss->state(); + ss->clear_state(); + + shader->has_surface = true; + } + else { + shader->osl_surface_ref = OSL::ShadingAttribStateRef(); + shader->osl_surface_bump_ref = 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; + + shader->osl_volume_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()); + shader->osl_volume_ref = OSL::ShadingAttribStateRef(); - ss->clear_state(); - - shader->has_surface = true; - } - else { - og->surface_state.push_back(OSL::ShadingAttribStateRef()); - og->surface_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 volume shader */ - if(shader->used && graph && output->input("Volume")->link) { - compile_type(shader, shader->graph, SHADER_TYPE_VOLUME); - shader->has_volume = 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->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()); - } + og->volume_state.push_back(shader->osl_volume_ref); + og->volume_state.push_back(shader->osl_volume_ref); - /* generate displacement shader */ - if(shader->used && graph && output->input("Displacement")->link) { - compile_type(shader, shader->graph, SHADER_TYPE_DISPLACEMENT); - shader->has_displacement = true; - - 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->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) { } diff --git a/intern/cycles/render/osl.h b/intern/cycles/render/osl.h index 90107a34a98..cee37c58d74 100644 --- a/intern/cycles/render/osl.h +++ b/intern/cycles/render/osl.h @@ -20,11 +20,14 @@ #define __OSL_H__ #include "util_set.h" +#include "util_string.h" #include "shader.h" #ifdef WITH_OSL +#include #include +#include #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 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& nodes); void *shadingsys; + void *manager; ShaderType current_type; Shader *current_shader; }; diff --git a/intern/cycles/render/shader.h b/intern/cycles/render/shader.h index 90ae67eecff..373b3356f51 100644 --- a/intern/cycles/render/shader.h +++ b/intern/cycles/render/shader.h @@ -27,6 +27,10 @@ #include "util_string.h" #include "util_types.h" +#ifdef WITH_OSL +#include +#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(); diff --git a/intern/cycles/util/util_path.cpp b/intern/cycles/util/util_path.cpp index a571fe81118..8cf23bc6a76 100644 --- a/intern/cycles/util/util_path.cpp +++ b/intern/cycles/util/util_path.cpp @@ -170,7 +170,7 @@ bool path_read_binary(const string& path, vector& binary) return true; } -static bool path_read_text(const string& path, string& text) +bool path_read_text(const string& path, string& text) { vector 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 diff --git a/intern/cycles/util/util_path.h b/intern/cycles/util/util_path.h index 6cba6a3158f..89e4452ecd9 100644 --- a/intern/cycles/util/util_path.h +++ b/intern/cycles/util/util_path.h @@ -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& binary); bool path_read_binary(const string& path, vector& 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);