From eb87529e23cdc744ed52b00f3de25e208b29d7f1 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sat, 3 Nov 2012 14:32:35 +0000 Subject: 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 ++++++++++-- 5 files changed, 369 insertions(+), 7 deletions(-) create mode 100644 intern/cycles/blender/addon/osl.py (limited to 'intern/cycles/blender') 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; -- cgit v1.2.3