Blender V2.61 - r43446

svm.cpp

Go to the documentation of this file.
00001 /*
00002  * Copyright 2011, Blender Foundation.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License
00006  * as published by the Free Software Foundation; either version 2
00007  * of the License, or (at your option) any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software Foundation,
00016  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00017  */
00018 
00019 #include "device.h"
00020 #include "graph.h"
00021 #include "light.h"
00022 #include "mesh.h"
00023 #include "scene.h"
00024 #include "shader.h"
00025 #include "svm.h"
00026 
00027 #include "util_debug.h"
00028 #include "util_foreach.h"
00029 #include "util_progress.h"
00030 
00031 CCL_NAMESPACE_BEGIN
00032 
00033 /* Shader Manager */
00034 
00035 SVMShaderManager::SVMShaderManager()
00036 {
00037 }
00038 
00039 SVMShaderManager::~SVMShaderManager()
00040 {
00041 }
00042 
00043 void SVMShaderManager::device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress& progress)
00044 {
00045     if(!need_update)
00046         return;
00047 
00048     /* test if we need to update */
00049     device_free(device, dscene);
00050 
00051     /* svm_nodes */
00052     vector<int4> svm_nodes;
00053     size_t i;
00054 
00055     for(i = 0; i < scene->shaders.size(); i++) {
00056         svm_nodes.push_back(make_int4(NODE_SHADER_JUMP, 0, 0, 0));
00057         svm_nodes.push_back(make_int4(NODE_SHADER_JUMP, 0, 0, 0));
00058     }
00059     
00060     bool sunsky_done = false;
00061     bool use_multi_closure = device->support_full_kernel();
00062 
00063     for(i = 0; i < scene->shaders.size(); i++) {
00064         Shader *shader = scene->shaders[i];
00065 
00066         if(progress.get_cancel()) return;
00067 
00068         assert(shader->graph);
00069 
00070         if(shader->sample_as_light && shader->has_surface_emission)
00071             scene->light_manager->need_update = true;
00072 
00073         SVMCompiler compiler(scene->shader_manager, scene->image_manager,
00074             use_multi_closure);
00075         compiler.sunsky = (sunsky_done)? NULL: &dscene->data.sunsky;
00076         compiler.background = ((int)i == scene->default_background);
00077         compiler.compile(shader, svm_nodes, i);
00078         if(!compiler.sunsky)
00079             sunsky_done = true;
00080     }
00081 
00082     dscene->svm_nodes.copy((uint4*)&svm_nodes[0], svm_nodes.size());
00083     device->tex_alloc("__svm_nodes", dscene->svm_nodes);
00084 
00085     for(i = 0; i < scene->shaders.size(); i++) {
00086         Shader *shader = scene->shaders[i];
00087         shader->need_update = false;
00088     }
00089 
00090     device_update_common(device, dscene, scene, progress);
00091 
00092     need_update = false;
00093 }
00094 
00095 void SVMShaderManager::device_free(Device *device, DeviceScene *dscene)
00096 {
00097     device_free_common(device, dscene);
00098 
00099     device->tex_free(dscene->svm_nodes);
00100     dscene->svm_nodes.clear();
00101 }
00102 
00103 /* Graph Compiler */
00104 
00105 SVMCompiler::SVMCompiler(ShaderManager *shader_manager_, ImageManager *image_manager_, bool use_multi_closure_)
00106 {
00107     shader_manager = shader_manager_;
00108     image_manager = image_manager_;
00109     sunsky = NULL;
00110     max_stack_use = 0;
00111     current_type = SHADER_TYPE_SURFACE;
00112     current_shader = NULL;
00113     background = false;
00114     mix_weight_offset = SVM_STACK_INVALID;
00115     use_multi_closure = use_multi_closure_;
00116 }
00117 
00118 int SVMCompiler::stack_size(ShaderSocketType type)
00119 {
00120     if(type == SHADER_SOCKET_FLOAT)
00121         return 1;
00122     else if(type == SHADER_SOCKET_COLOR)
00123         return 3;
00124     else if(type == SHADER_SOCKET_VECTOR)
00125         return 3;
00126     else if(type == SHADER_SOCKET_NORMAL)
00127         return 3;
00128     else if(type == SHADER_SOCKET_POINT)
00129         return 3;
00130     else if(type == SHADER_SOCKET_CLOSURE)
00131         return 0;
00132 
00133     assert(0);
00134     return 0;
00135 }
00136 
00137 int SVMCompiler::stack_find_offset(ShaderSocketType type)
00138 {
00139     int size = stack_size(type);
00140     int offset = -1;
00141     
00142     /* find free space in stack & mark as used */
00143     for(int i = 0, num_unused = 0; i < SVM_STACK_SIZE; i++) {
00144         if(active_stack.users[i]) num_unused = 0;
00145         else num_unused++;
00146 
00147         if(num_unused == size) {
00148             offset = i+1 - size;
00149             max_stack_use = max(i+1, max_stack_use);
00150 
00151             while(i >= offset)
00152                 active_stack.users[i--] = 1;
00153 
00154             return offset;
00155         }
00156     }
00157 
00158     fprintf(stderr, "Out of SVM stack space.\n");
00159     assert(0);
00160 
00161     return offset;
00162 }
00163 
00164 void SVMCompiler::stack_backup(StackBackup& backup, set<ShaderNode*>& done)
00165 {
00166     backup.done = done;
00167     backup.stack = active_stack;
00168 
00169     foreach(ShaderNode *node, current_graph->nodes) {
00170         foreach(ShaderInput *input, node->inputs)
00171             backup.offsets.push_back(input->stack_offset);
00172         foreach(ShaderOutput *output, node->outputs)
00173             backup.offsets.push_back(output->stack_offset);
00174     }
00175 }
00176 
00177 void SVMCompiler::stack_restore(StackBackup& backup, set<ShaderNode*>& done)
00178 {
00179     int i = 0;
00180 
00181     done = backup.done;
00182     active_stack = backup.stack;
00183 
00184     foreach(ShaderNode *node, current_graph->nodes) {
00185         foreach(ShaderInput *input, node->inputs)
00186             input->stack_offset = backup.offsets[i++];
00187         foreach(ShaderOutput *output, node->outputs)
00188             output->stack_offset = backup.offsets[i++];
00189     }
00190 }
00191 
00192 void SVMCompiler::stack_assign(ShaderInput *input)
00193 {
00194     /* stack offset assign? */
00195     if(input->stack_offset == SVM_STACK_INVALID) {
00196         if(input->link) {
00197             /* linked to output -> use output offset */
00198             input->stack_offset = input->link->stack_offset;
00199         }
00200         else {
00201             /* not linked to output -> add nodes to load default value */
00202             input->stack_offset = stack_find_offset(input->type);
00203 
00204             if(input->type == SHADER_SOCKET_FLOAT) {
00205                 add_node(NODE_VALUE_F, __float_as_int(input->value.x), input->stack_offset);
00206             }
00207             else if(input->type == SHADER_SOCKET_VECTOR ||
00208                 input->type == SHADER_SOCKET_NORMAL ||
00209                 input->type == SHADER_SOCKET_POINT ||
00210                 input->type == SHADER_SOCKET_COLOR) {
00211 
00212                 add_node(NODE_VALUE_V, input->stack_offset);
00213                 add_node(NODE_VALUE_V, input->value);
00214             }
00215             else /* should not get called for closure */
00216                 assert(0);
00217         }
00218     }
00219 }
00220 
00221 void SVMCompiler::stack_assign(ShaderOutput *output)
00222 {
00223     /* if no stack offset assigned yet, find one */
00224     if(output->stack_offset == SVM_STACK_INVALID)
00225         output->stack_offset = stack_find_offset(output->type);
00226 }
00227 
00228 void SVMCompiler::stack_link(ShaderInput *input, ShaderOutput *output)
00229 {
00230     if(output->stack_offset == SVM_STACK_INVALID) {
00231         assert(input->link);
00232         assert(stack_size(output->type) == stack_size(input->link->type));
00233 
00234         output->stack_offset = input->link->stack_offset;
00235 
00236         int size = stack_size(output->type);
00237 
00238         for(int i = 0; i < size; i++)
00239             active_stack.users[output->stack_offset + i]++;
00240     }
00241 }
00242 
00243 void SVMCompiler::stack_clear_users(ShaderNode *node, set<ShaderNode*>& done)
00244 {
00245     /* optimization we should add:
00246        find and lower user counts for outputs for which all inputs are done.
00247        this is done before the node is compiled, under the assumption that the
00248        node will first load all inputs from the stack and then writes its
00249        outputs. this used to work, but was disabled because it gave trouble
00250        with inputs getting stack positions assigned */
00251 
00252     foreach(ShaderInput *input, node->inputs) {
00253         ShaderOutput *output = input->link;
00254 
00255         if(output && output->stack_offset != SVM_STACK_INVALID) {
00256             bool all_done = true;
00257 
00258             /* optimization we should add: verify if in->parent is actually used */
00259             foreach(ShaderInput *in, output->links)
00260                 if(in->parent != node && done.find(in->parent) == done.end())
00261                     all_done = false;
00262 
00263             if(all_done) {
00264                 int size = stack_size(output->type);
00265 
00266                 for(int i = 0; i < size; i++)
00267                     active_stack.users[output->stack_offset + i]--;
00268 
00269                 output->stack_offset = SVM_STACK_INVALID;
00270 
00271                 foreach(ShaderInput *in, output->links)
00272                     in->stack_offset = SVM_STACK_INVALID;
00273             }
00274         }
00275     }
00276 }
00277 
00278 void SVMCompiler::stack_clear_temporary(ShaderNode *node)
00279 {
00280     foreach(ShaderInput *input, node->inputs) {
00281         if(!input->link && input->stack_offset != SVM_STACK_INVALID) {
00282             int size = stack_size(input->type);
00283 
00284             for(int i = 0; i < size; i++)
00285                 active_stack.users[input->stack_offset + i]--;
00286 
00287             input->stack_offset = SVM_STACK_INVALID;
00288         }
00289     }
00290 }
00291 
00292 uint SVMCompiler::encode_uchar4(uint x, uint y, uint z, uint w)
00293 {
00294     assert(x <= 255);
00295     assert(y <= 255);
00296     assert(z <= 255);
00297     assert(w <= 255);
00298 
00299     return (x) | (y << 8) | (z << 16) | (w << 24);
00300 }
00301 
00302 void SVMCompiler::add_node(int a, int b, int c, int d)
00303 {
00304     svm_nodes.push_back(make_int4(a, b, c, d));
00305 }
00306 
00307 void SVMCompiler::add_node(NodeType type, int a, int b, int c)
00308 {
00309     svm_nodes.push_back(make_int4(type, a, b, c));
00310 }
00311 
00312 void SVMCompiler::add_node(NodeType type, const float3& f)
00313 {
00314     svm_nodes.push_back(make_int4(type,
00315         __float_as_int(f.x),
00316         __float_as_int(f.y),
00317         __float_as_int(f.z)));
00318 }
00319 
00320 void SVMCompiler::add_node(const float4& f)
00321 {
00322     svm_nodes.push_back(make_int4(
00323         __float_as_int(f.x),
00324         __float_as_int(f.y),
00325         __float_as_int(f.z),
00326         __float_as_int(f.w)));
00327 }
00328 
00329 uint SVMCompiler::attribute(ustring name)
00330 {
00331     return shader_manager->get_attribute_id(name);
00332 }
00333 
00334 uint SVMCompiler::attribute(Attribute::Standard std)
00335 {
00336     return shader_manager->get_attribute_id(std);
00337 }
00338 
00339 bool SVMCompiler::node_skip_input(ShaderNode *node, ShaderInput *input)
00340 {
00341     /* nasty exception .. */
00342     if(current_type == SHADER_TYPE_DISPLACEMENT && input->link && input->link->parent->name == ustring("bump"))
00343         return true;
00344     
00345     return false;
00346 }
00347 
00348 void SVMCompiler::find_dependencies(set<ShaderNode*>& dependencies, const set<ShaderNode*>& done, ShaderInput *input)
00349 {
00350     ShaderNode *node = (input->link)? input->link->parent: NULL;
00351 
00352     if(node && done.find(node) == done.end()) {
00353         foreach(ShaderInput *in, node->inputs)
00354             if(!node_skip_input(node, in))
00355                 find_dependencies(dependencies, done, in);
00356 
00357         dependencies.insert(node);
00358     }
00359 }
00360 
00361 void SVMCompiler::generate_svm_nodes(const set<ShaderNode*>& nodes, set<ShaderNode*>& done)
00362 {
00363     bool nodes_done;
00364 
00365     do {
00366         nodes_done = true;
00367 
00368         foreach(ShaderNode *node, nodes) {
00369             if(done.find(node) == done.end()) {
00370                 bool inputs_done = true;
00371 
00372                 foreach(ShaderInput *input, node->inputs)
00373                     if(!node_skip_input(node, input))
00374                         if(input->link && done.find(input->link->parent) == done.end())
00375                             inputs_done = false;
00376 
00377                 if(inputs_done) {
00378                     node->compile(*this);
00379                     stack_clear_users(node, done);
00380                     stack_clear_temporary(node);
00381                     done.insert(node);
00382                 }
00383                 else
00384                     nodes_done = false;
00385             }
00386         }
00387     } while(!nodes_done);
00388 }
00389 
00390 void SVMCompiler::generate_closure(ShaderNode *node, set<ShaderNode*>& done)
00391 {
00392     if(node->name == ustring("mix_closure") || node->name == ustring("add_closure")) {
00393         ShaderInput *fin = node->input("Fac");
00394         ShaderInput *cl1in = node->input("Closure1");
00395         ShaderInput *cl2in = node->input("Closure2");
00396 
00397         /* execute dependencies for mix weight */
00398         if(fin) {
00399             set<ShaderNode*> dependencies;
00400             find_dependencies(dependencies, done, fin);
00401             generate_svm_nodes(dependencies, done);
00402 
00403             /* add mix node */
00404             stack_assign(fin);
00405         }
00406 
00407         int mix_offset = svm_nodes.size();
00408 
00409         if(fin)
00410             add_node(NODE_MIX_CLOSURE, fin->stack_offset, 0, 0);
00411         else
00412             add_node(NODE_ADD_CLOSURE, 0, 0, 0);
00413 
00414         /* generate code for closure 1
00415            note we backup all compiler state and restore it afterwards, so one
00416            closure choice doesn't influence the other*/
00417         if(cl1in->link) {
00418             StackBackup backup;
00419             stack_backup(backup, done);
00420 
00421             generate_closure(cl1in->link->parent, done);
00422             add_node(NODE_END, 0, 0, 0);
00423 
00424             stack_restore(backup, done);
00425         }
00426         else
00427             add_node(NODE_END, 0, 0, 0);
00428 
00429         /* generate code for closure 2 */
00430         int cl2_offset = svm_nodes.size();
00431 
00432         if(cl2in->link) {
00433             StackBackup backup;
00434             stack_backup(backup, done);
00435 
00436             generate_closure(cl2in->link->parent, done);
00437             add_node(NODE_END, 0, 0, 0);
00438 
00439             stack_restore(backup, done);
00440         }
00441         else
00442             add_node(NODE_END, 0, 0, 0);
00443 
00444         /* set jump for mix node, -1 because offset is already
00445            incremented when this jump is added to it */
00446         svm_nodes[mix_offset].z = cl2_offset - mix_offset - 1;
00447 
00448         done.insert(node);
00449         stack_clear_users(node, done);
00450         stack_clear_temporary(node);
00451     }
00452     else {
00453         /* execute dependencies for closure */
00454         foreach(ShaderInput *in, node->inputs) {
00455             if(!node_skip_input(node, in) && in->link) {
00456                 set<ShaderNode*> dependencies;
00457                 find_dependencies(dependencies, done, in);
00458                 generate_svm_nodes(dependencies, done);
00459             }
00460         }
00461 
00462         /* compile closure itself */
00463         node->compile(*this);
00464         stack_clear_users(node, done);
00465         stack_clear_temporary(node);
00466 
00467         if(node->name == ustring("emission"))
00468             current_shader->has_surface_emission = true;
00469         if(node->name == ustring("transparent"))
00470             current_shader->has_surface_transparent = true;
00471 
00472         /* end node is added outside of this */
00473     }
00474 }
00475 
00476 void SVMCompiler::generate_multi_closure(ShaderNode *node, set<ShaderNode*>& done, uint in_offset)
00477 {
00478     /* todo: the weaks point here is that unlike the single closure sampling 
00479        we will evaluate all nodes even if they are used as input for closures
00480        that are unused. it's not clear what would be the best way to skip such
00481        nodes at runtime, especially if they are tangled up  */
00482 
00483     if(node->name == ustring("mix_closure") || node->name == ustring("add_closure")) {
00484         ShaderInput *fin = node->input("Fac");
00485         ShaderInput *cl1in = node->input("Closure1");
00486         ShaderInput *cl2in = node->input("Closure2");
00487 
00488         uint out1_offset = SVM_STACK_INVALID;
00489         uint out2_offset = SVM_STACK_INVALID;
00490 
00491         if(fin) {
00492             /* mix closure */
00493             set<ShaderNode*> dependencies;
00494             find_dependencies(dependencies, done, fin);
00495             generate_svm_nodes(dependencies, done);
00496 
00497             stack_assign(fin);
00498 
00499             if(cl1in->link)
00500                 out1_offset = stack_find_offset(SHADER_SOCKET_FLOAT);
00501             if(cl2in->link)
00502                 out2_offset = stack_find_offset(SHADER_SOCKET_FLOAT);
00503 
00504             add_node(NODE_MIX_CLOSURE, 
00505                 encode_uchar4(fin->stack_offset, in_offset, out1_offset, out2_offset));
00506         }
00507         else {
00508             /* add closure */
00509             out1_offset = in_offset;
00510             out2_offset = in_offset;
00511         }
00512 
00513         if(cl1in->link) {
00514             generate_multi_closure(cl1in->link->parent, done, out1_offset);
00515 
00516             if(fin)
00517                 active_stack.users[out1_offset]--;
00518         }
00519 
00520         if(cl2in->link) {
00521             generate_multi_closure(cl2in->link->parent, done, out2_offset);
00522 
00523             if(fin)
00524                 active_stack.users[out2_offset]--;
00525         }
00526     }
00527     else {
00528         /* execute dependencies for closure */
00529         foreach(ShaderInput *in, node->inputs) {
00530             if(!node_skip_input(node, in) && in->link) {
00531                 set<ShaderNode*> dependencies;
00532                 find_dependencies(dependencies, done, in);
00533                 generate_svm_nodes(dependencies, done);
00534             }
00535         }
00536 
00537         mix_weight_offset = in_offset;
00538 
00539         /* compile closure itself */
00540         node->compile(*this);
00541         stack_clear_users(node, done);
00542         stack_clear_temporary(node);
00543 
00544         mix_weight_offset = SVM_STACK_INVALID;
00545 
00546         if(node->name == ustring("emission"))
00547             current_shader->has_surface_emission = true;
00548         if(node->name == ustring("transparent"))
00549             current_shader->has_surface_transparent = true;
00550 
00551         /* end node is added outside of this */
00552     }
00553 }
00554 
00555 
00556 void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType type)
00557 {
00558     /* Converting a shader graph into svm_nodes that can be executed
00559      * sequentially on the virtual machine is fairly simple. We can keep
00560      * looping over nodes and each time all the inputs of a node are
00561      * ready, we add svm_nodes for it that read the inputs from the
00562      * stack and write outputs back to the stack.
00563      *
00564      * With the SVM, we always sample only a single closure. We can think
00565      * of all closures nodes as a binary tree with mix closures as inner
00566      * nodes and other closures as leafs. The SVM will traverse that tree,
00567      * each time deciding to go left or right depending on the mix weights,
00568      * until a closure is found.
00569      *
00570      * We only execute nodes that are needed for the mix weights and chosen
00571      * closure.
00572      */
00573 
00574     current_type = type;
00575     current_graph = graph;
00576 
00577     /* get input in output node */
00578     ShaderNode *node = graph->output();
00579     ShaderInput *clin = NULL;
00580     
00581     if(type == SHADER_TYPE_SURFACE)
00582         clin = node->input("Surface");
00583     else if(type == SHADER_TYPE_VOLUME)
00584         clin = node->input("Volume");
00585     else if(type == SHADER_TYPE_DISPLACEMENT)
00586         clin = node->input("Displacement");
00587     else
00588         assert(0);
00589 
00590     /* clear all compiler state */
00591     memset(&active_stack, 0, sizeof(active_stack));
00592     svm_nodes.clear();
00593 
00594     foreach(ShaderNode *node, graph->nodes) {
00595         foreach(ShaderInput *input, node->inputs)
00596             input->stack_offset = SVM_STACK_INVALID;
00597         foreach(ShaderOutput *output, node->outputs)
00598             output->stack_offset = SVM_STACK_INVALID;
00599     }
00600 
00601     if(clin->link) {
00602         bool generate = false;
00603         if(type == SHADER_TYPE_SURFACE) {
00604             /* generate surface shader */
00605             generate = true;
00606             shader->has_surface = true;
00607         }
00608         else if(type == SHADER_TYPE_VOLUME) {
00609             /* generate volume shader */
00610             generate = true;
00611             shader->has_volume = true;
00612         }
00613         else if(type == SHADER_TYPE_DISPLACEMENT) {
00614             /* generate displacement shader */
00615             generate = true;
00616             shader->has_displacement = true;
00617         }
00618 
00619         if(generate) {
00620             set<ShaderNode*> done;
00621 
00622             if(use_multi_closure)
00623                 generate_multi_closure(clin->link->parent, done, SVM_STACK_INVALID);
00624             else
00625                 generate_closure(clin->link->parent, done);
00626         }
00627     }
00628 
00629     /* compile output node */
00630     node->compile(*this);
00631 
00632     add_node(NODE_END, 0, 0, 0);
00633 }
00634 
00635 void SVMCompiler::compile(Shader *shader, vector<int4>& global_svm_nodes, int index)
00636 {
00637     /* copy graph for shader with bump mapping */
00638     ShaderNode *node = shader->graph->output();
00639 
00640     if(node->input("Surface")->link && node->input("Displacement")->link)
00641         if(!shader->graph_bump)
00642             shader->graph_bump = shader->graph->copy();
00643 
00644     /* finalize */
00645     shader->graph->finalize(false, false);
00646     if(shader->graph_bump)
00647         shader->graph_bump->finalize(true, false);
00648 
00649     current_shader = shader;
00650 
00651     shader->has_surface = false;
00652     shader->has_surface_emission = false;
00653     shader->has_surface_transparent = false;
00654     shader->has_volume = false;
00655     shader->has_displacement = false;
00656 
00657     /* generate surface shader */
00658     compile_type(shader, shader->graph, SHADER_TYPE_SURFACE);
00659     global_svm_nodes[index*2 + 0].y = global_svm_nodes.size();
00660     global_svm_nodes[index*2 + 1].y = global_svm_nodes.size();
00661     global_svm_nodes.insert(global_svm_nodes.end(), svm_nodes.begin(), svm_nodes.end());
00662 
00663     if(shader->graph_bump) {
00664         compile_type(shader, shader->graph_bump, SHADER_TYPE_SURFACE);
00665         global_svm_nodes[index*2 + 1].y = global_svm_nodes.size();
00666         global_svm_nodes.insert(global_svm_nodes.end(), svm_nodes.begin(), svm_nodes.end());
00667     }
00668 
00669     /* generate volume shader */
00670     compile_type(shader, shader->graph, SHADER_TYPE_VOLUME);
00671     global_svm_nodes[index*2 + 0].z = global_svm_nodes.size();
00672     global_svm_nodes[index*2 + 1].z = global_svm_nodes.size();
00673     global_svm_nodes.insert(global_svm_nodes.end(), svm_nodes.begin(), svm_nodes.end());
00674 
00675     /* generate displacement shader */
00676     compile_type(shader, shader->graph, SHADER_TYPE_DISPLACEMENT);
00677     global_svm_nodes[index*2 + 0].w = global_svm_nodes.size();
00678     global_svm_nodes[index*2 + 1].w = global_svm_nodes.size();
00679     global_svm_nodes.insert(global_svm_nodes.end(), svm_nodes.begin(), svm_nodes.end());
00680 }
00681 
00682 CCL_NAMESPACE_END
00683