Cycles microdisplacement: Improved automatic bump mapping

Object coordinates can now be used in the displacement shader and will give
correct results, where as before bump mapping was calculated from the displace
positions and resulted in incorrect shading.

This works by evaluating the shader in two parts, first bump then surface, and
setting the shader state to match what it would be if the surface was
undisplaced for the bump shader evaluation. Currently only `P` is set as if
undisplaced, but other shader variables could be set as well, such as `I` or
`time`. Since these aren't set to anything meaningful for displacement I left
them out of this patch, we can decide what to do with them separately.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D2156
This commit is contained in:
Mai Lavelle
2016-08-14 11:44:25 -04:00
parent 62aecbdac1
commit e7ea1ae78c
14 changed files with 181 additions and 52 deletions

View File

@@ -113,6 +113,7 @@ set(SRC_SVM_HEADERS
svm/svm.h svm/svm.h
svm/svm_attribute.h svm/svm_attribute.h
svm/svm_blackbody.h svm/svm_blackbody.h
svm/svm_bump.h
svm/svm_camera.h svm/svm_camera.h
svm/svm_closure.h svm/svm_closure.h
svm/svm_convert.h svm/svm_convert.h

View File

@@ -714,20 +714,21 @@ enum ShaderDataFlag {
SD_VOLUME_MIS = (1 << 19), /* use multiple importance sampling */ SD_VOLUME_MIS = (1 << 19), /* use multiple importance sampling */
SD_VOLUME_CUBIC = (1 << 20), /* use cubic interpolation for voxels */ SD_VOLUME_CUBIC = (1 << 20), /* use cubic interpolation for voxels */
SD_HAS_BUMP = (1 << 21), /* has data connected to the displacement input */ SD_HAS_BUMP = (1 << 21), /* has data connected to the displacement input */
SD_HAS_DISPLACEMENT = (1 << 22), /* has true displacement */
SD_SHADER_FLAGS = (SD_USE_MIS|SD_HAS_TRANSPARENT_SHADOW|SD_HAS_VOLUME| SD_SHADER_FLAGS = (SD_USE_MIS|SD_HAS_TRANSPARENT_SHADOW|SD_HAS_VOLUME|
SD_HAS_ONLY_VOLUME|SD_HETEROGENEOUS_VOLUME| SD_HAS_ONLY_VOLUME|SD_HETEROGENEOUS_VOLUME|
SD_HAS_BSSRDF_BUMP|SD_VOLUME_EQUIANGULAR|SD_VOLUME_MIS| SD_HAS_BSSRDF_BUMP|SD_VOLUME_EQUIANGULAR|SD_VOLUME_MIS|
SD_VOLUME_CUBIC|SD_HAS_BUMP), SD_VOLUME_CUBIC|SD_HAS_BUMP|SD_HAS_DISPLACEMENT),
/* object flags */ /* object flags */
SD_HOLDOUT_MASK = (1 << 22), /* holdout for camera rays */ SD_HOLDOUT_MASK = (1 << 23), /* holdout for camera rays */
SD_OBJECT_MOTION = (1 << 23), /* has object motion blur */ SD_OBJECT_MOTION = (1 << 24), /* has object motion blur */
SD_TRANSFORM_APPLIED = (1 << 24), /* vertices have transform applied */ SD_TRANSFORM_APPLIED = (1 << 25), /* vertices have transform applied */
SD_NEGATIVE_SCALE_APPLIED = (1 << 25), /* vertices have negative scale applied */ SD_NEGATIVE_SCALE_APPLIED = (1 << 26), /* vertices have negative scale applied */
SD_OBJECT_HAS_VOLUME = (1 << 26), /* object has a volume shader */ SD_OBJECT_HAS_VOLUME = (1 << 27), /* object has a volume shader */
SD_OBJECT_INTERSECTS_VOLUME = (1 << 27), /* object intersects AABB of an object with volume shader */ SD_OBJECT_INTERSECTS_VOLUME = (1 << 28), /* object intersects AABB of an object with volume shader */
SD_OBJECT_HAS_VERTEX_MOTION = (1 << 28), /* has position for motion vertices */ SD_OBJECT_HAS_VERTEX_MOTION = (1 << 29), /* has position for motion vertices */
SD_OBJECT_FLAGS = (SD_HOLDOUT_MASK|SD_OBJECT_MOTION|SD_TRANSFORM_APPLIED| SD_OBJECT_FLAGS = (SD_HOLDOUT_MASK|SD_OBJECT_MOTION|SD_TRANSFORM_APPLIED|
SD_NEGATIVE_SCALE_APPLIED|SD_OBJECT_HAS_VOLUME| SD_NEGATIVE_SCALE_APPLIED|SD_OBJECT_HAS_VOLUME|

View File

@@ -54,6 +54,7 @@ struct OSLGlobals {
vector<OSL::ShaderGroupRef> surface_state; vector<OSL::ShaderGroupRef> surface_state;
vector<OSL::ShaderGroupRef> volume_state; vector<OSL::ShaderGroupRef> volume_state;
vector<OSL::ShaderGroupRef> displacement_state; vector<OSL::ShaderGroupRef> displacement_state;
vector<OSL::ShaderGroupRef> bump_state;
OSL::ShaderGroupRef background_state; OSL::ShaderGroupRef background_state;
/* attributes */ /* attributes */

View File

@@ -93,6 +93,7 @@ ustring OSLRenderServices::u_geom_numpolyvertices("geom:numpolyvertices");
ustring OSLRenderServices::u_geom_trianglevertices("geom:trianglevertices"); ustring OSLRenderServices::u_geom_trianglevertices("geom:trianglevertices");
ustring OSLRenderServices::u_geom_polyvertices("geom:polyvertices"); ustring OSLRenderServices::u_geom_polyvertices("geom:polyvertices");
ustring OSLRenderServices::u_geom_name("geom:name"); ustring OSLRenderServices::u_geom_name("geom:name");
ustring OSLRenderServices::u_geom_undisplaced("geom:undisplaced");
ustring OSLRenderServices::u_is_smooth("geom:is_smooth"); ustring OSLRenderServices::u_is_smooth("geom:is_smooth");
#ifdef __HAIR__ #ifdef __HAIR__
ustring OSLRenderServices::u_is_curve("geom:is_curve"); ustring OSLRenderServices::u_is_curve("geom:is_curve");

View File

@@ -158,6 +158,7 @@ public:
static ustring u_geom_trianglevertices; static ustring u_geom_trianglevertices;
static ustring u_geom_polyvertices; static ustring u_geom_polyvertices;
static ustring u_geom_name; static ustring u_geom_name;
static ustring u_geom_undisplaced;
static ustring u_is_smooth; static ustring u_is_smooth;
static ustring u_is_curve; static ustring u_is_curve;
static ustring u_curve_thickness; static ustring u_curve_thickness;

View File

@@ -184,6 +184,47 @@ void OSLShader::eval_surface(KernelGlobals *kg, ShaderData *sd, PathState *state
OSL::ShadingContext *octx = tdata->context[(int)ctx]; OSL::ShadingContext *octx = tdata->context[(int)ctx];
int shader = sd->shader & SHADER_MASK; int shader = sd->shader & SHADER_MASK;
/* automatic bump shader */
if(kg->osl->bump_state[shader]) {
/* save state */
float3 P = sd->P;
float3 dPdx = sd->dP.dx;
float3 dPdy = sd->dP.dy;
/* set state as if undisplaced */
if(sd->flag & SD_HAS_DISPLACEMENT) {
float data[9];
bool found = kg->osl->services->get_attribute(sd, true, OSLRenderServices::u_empty, TypeDesc::TypeVector,
OSLRenderServices::u_geom_undisplaced, data);
assert(found);
memcpy(&sd->P, data, sizeof(float)*3);
memcpy(&sd->dP.dx, data+3, sizeof(float)*3);
memcpy(&sd->dP.dy, data+6, sizeof(float)*3);
object_position_transform(kg, sd, &sd->P);
object_dir_transform(kg, sd, &sd->dP.dx);
object_dir_transform(kg, sd, &sd->dP.dy);
globals->P = TO_VEC3(sd->P);
globals->dPdx = TO_VEC3(sd->dP.dx);
globals->dPdy = TO_VEC3(sd->dP.dy);
}
/* execute bump shader */
ss->execute(octx, *(kg->osl->bump_state[shader]), *globals);
/* reset state */
sd->P = P;
sd->dP.dx = dPdx;
sd->dP.dy = dPdy;
globals->P = TO_VEC3(P);
globals->dPdx = TO_VEC3(dPdx);
globals->dPdy = TO_VEC3(dPdy);
}
/* surface shader */
if(kg->osl->surface_state[shader]) { if(kg->osl->surface_state[shader]) {
ss->execute(octx, *(kg->osl->surface_state[shader]), *globals); ss->execute(octx, *(kg->osl->surface_state[shader]), *globals);
} }

View File

@@ -181,6 +181,7 @@ CCL_NAMESPACE_END
#include "svm_brick.h" #include "svm_brick.h"
#include "svm_vector_transform.h" #include "svm_vector_transform.h"
#include "svm_voxel.h" #include "svm_voxel.h"
#include "svm_bump.h"
CCL_NAMESPACE_BEGIN CCL_NAMESPACE_BEGIN
@@ -294,6 +295,12 @@ ccl_device_noinline void svm_eval_nodes(KernelGlobals *kg, ShaderData *sd, ccl_a
case NODE_CLOSURE_SET_NORMAL: case NODE_CLOSURE_SET_NORMAL:
svm_node_set_normal(kg, sd, stack, node.y, node.z); svm_node_set_normal(kg, sd, stack, node.y, node.z);
break; break;
case NODE_ENTER_BUMP_EVAL:
svm_node_enter_bump_eval(kg, sd, stack, node.y);
break;
case NODE_LEAVE_BUMP_EVAL:
svm_node_leave_bump_eval(kg, sd, stack, node.y);
break;
# endif /* NODES_FEATURE(NODE_FEATURE_BUMP) */ # endif /* NODES_FEATURE(NODE_FEATURE_BUMP) */
case NODE_HSV: case NODE_HSV:
svm_node_hsv(kg, sd, stack, node, &offset); svm_node_hsv(kg, sd, stack, node, &offset);

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2011-2016 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
CCL_NAMESPACE_BEGIN
/* Bump Eval Nodes */
ccl_device void svm_node_enter_bump_eval(KernelGlobals *kg, ShaderData *sd, float *stack, uint offset)
{
/* save state */
stack_store_float3(stack, offset+0, ccl_fetch(sd, P));
stack_store_float3(stack, offset+3, ccl_fetch(sd, dP).dx);
stack_store_float3(stack, offset+6, ccl_fetch(sd, dP).dy);
/* set state as if undisplaced */
const AttributeDescriptor desc = find_attribute(kg, sd, ATTR_STD_POSITION_UNDISPLACED);
if(desc.offset != ATTR_STD_NOT_FOUND) {
float3 P, dPdx, dPdy;
P = primitive_attribute_float3(kg, sd, desc, &dPdx, &dPdy);
object_position_transform(kg, sd, &P);
object_dir_transform(kg, sd, &dPdx);
object_dir_transform(kg, sd, &dPdy);
ccl_fetch(sd, P) = P;
ccl_fetch(sd, dP).dx = dPdx;
ccl_fetch(sd, dP).dy = dPdy;
}
}
ccl_device void svm_node_leave_bump_eval(KernelGlobals *kg, ShaderData *sd, float *stack, uint offset)
{
/* restore state */
ccl_fetch(sd, P) = stack_load_float3(stack, offset+0);
ccl_fetch(sd, dP).dx = stack_load_float3(stack, offset+3);
ccl_fetch(sd, dP).dy = stack_load_float3(stack, offset+6);
}
CCL_NAMESPACE_END

View File

@@ -26,6 +26,8 @@ CCL_NAMESPACE_BEGIN
/* SVM stack offsets with this value indicate that it's not on the stack */ /* SVM stack offsets with this value indicate that it's not on the stack */
#define SVM_STACK_INVALID 255 #define SVM_STACK_INVALID 255
#define SVM_BUMP_EVAL_STATE_SIZE 9
/* Nodes */ /* Nodes */
/* Known frequencies of used nodes, used for selective nodes compilation /* Known frequencies of used nodes, used for selective nodes compilation
@@ -127,6 +129,8 @@ typedef enum ShaderNodeType {
NODE_HAIR_INFO, NODE_HAIR_INFO,
NODE_UVMAP, NODE_UVMAP,
NODE_TEX_VOXEL, NODE_TEX_VOXEL,
NODE_ENTER_BUMP_EVAL,
NODE_LEAVE_BUMP_EVAL,
} ShaderNodeType; } ShaderNodeType;
typedef enum NodeAttributeType { typedef enum NodeAttributeType {
@@ -374,7 +378,8 @@ typedef enum NodeTexVoxelSpace {
typedef enum ShaderType { typedef enum ShaderType {
SHADER_TYPE_SURFACE, SHADER_TYPE_SURFACE,
SHADER_TYPE_VOLUME, SHADER_TYPE_VOLUME,
SHADER_TYPE_DISPLACEMENT SHADER_TYPE_DISPLACEMENT,
SHADER_TYPE_BUMP,
} ShaderType; } ShaderType;
/* Closure */ /* Closure */

View File

@@ -856,27 +856,8 @@ void ShaderGraph::bump_from_displacement()
/* connect the bump out to the set normal in: */ /* connect the bump out to the set normal in: */
connect(bump->output("Normal"), set_normal->input("Direction")); connect(bump->output("Normal"), set_normal->input("Direction"));
/* connect bump output to normal input nodes that aren't set yet. actually /* connect to output node */
* this will only set the normal input to the geometry node that we created connect(set_normal->output("Normal"), output()->input("Normal"));
* and connected to all other normal inputs already. */
foreach(ShaderNode *node, nodes) {
/* Don't connect normal to the bump node we're coming from,
* otherwise it'll be a cycle in graph.
*/
if(node == bump) {
continue;
}
foreach(ShaderInput *input, node->inputs) {
if(!input->link && (input->flags() & SocketType::LINK_NORMAL))
connect(set_normal->output("Normal"), input);
}
}
/* for displacement bump, clear the normal input in case the above loop
* connected the setnormal out to the bump normalin */
ShaderInput *bump_normal_in = bump->input("Normal");
if(bump_normal_in)
bump_normal_in->link = NULL;
/* finally, add the copied nodes to the graph. we can't do this earlier /* finally, add the copied nodes to the graph. we can't do this earlier
* because we would create dependency cycles in the above loop */ * because we would create dependency cycles in the above loop */

View File

@@ -609,7 +609,7 @@ bool OSLCompiler::node_skip_input(ShaderNode *node, ShaderInput *input)
return true; return true;
if(input->name() == "Displacement" && current_type != SHADER_TYPE_DISPLACEMENT) if(input->name() == "Displacement" && current_type != SHADER_TYPE_DISPLACEMENT)
return true; return true;
if(input->name() == "Normal") if(input->name() == "Normal" && current_type != SHADER_TYPE_BUMP)
return true; return true;
} }
else if(node->special_type == SHADER_SPECIAL_TYPE_BUMP) { else if(node->special_type == SHADER_SPECIAL_TYPE_BUMP) {
@@ -684,6 +684,8 @@ void OSLCompiler::add(ShaderNode *node, const char *name, bool isfilepath)
ss->Shader("surface", name, id(node).c_str()); ss->Shader("surface", name, id(node).c_str());
else if(current_type == SHADER_TYPE_DISPLACEMENT) else if(current_type == SHADER_TYPE_DISPLACEMENT)
ss->Shader("displacement", name, id(node).c_str()); ss->Shader("displacement", name, id(node).c_str());
else if(current_type == SHADER_TYPE_BUMP)
ss->Shader("displacement", name, id(node).c_str());
else else
assert(0); assert(0);
@@ -1055,6 +1057,12 @@ OSL::ShaderGroupRef OSLCompiler::compile_type(Shader *shader, ShaderGraph *graph
generate_nodes(dependencies); generate_nodes(dependencies);
output->compile(*this); output->compile(*this);
} }
else if(type == SHADER_TYPE_BUMP) {
/* generate bump shader */
find_dependencies(dependencies, output->input("Normal"));
generate_nodes(dependencies);
output->compile(*this);
}
else if(type == SHADER_TYPE_VOLUME) { else if(type == SHADER_TYPE_VOLUME) {
/* generate volume shader */ /* generate volume shader */
find_dependencies(dependencies, output->input("Volume")); find_dependencies(dependencies, output->input("Volume"));
@@ -1116,10 +1124,10 @@ void OSLCompiler::compile(Scene *scene, OSLGlobals *og, Shader *shader)
if(shader->used && graph && output->input("Surface")->link) { if(shader->used && graph && output->input("Surface")->link) {
shader->osl_surface_ref = compile_type(shader, shader->graph, SHADER_TYPE_SURFACE); shader->osl_surface_ref = compile_type(shader, shader->graph, SHADER_TYPE_SURFACE);
if(shader->graph_bump) if(shader->graph_bump && shader->displacement_method != DISPLACE_TRUE)
shader->osl_surface_bump_ref = compile_type(shader, shader->graph_bump, SHADER_TYPE_SURFACE); shader->osl_surface_bump_ref = compile_type(shader, shader->graph_bump, SHADER_TYPE_BUMP);
else else
shader->osl_surface_bump_ref = shader->osl_surface_ref; shader->osl_surface_bump_ref = OSL::ShaderGroupRef();
shader->has_surface = true; shader->has_surface = true;
} }
@@ -1146,15 +1154,10 @@ void OSLCompiler::compile(Scene *scene, OSLGlobals *og, Shader *shader)
} }
/* push state to array for lookup */ /* push state to array for lookup */
if(shader->displacement_method == DISPLACE_TRUE || !shader->graph_bump) { og->surface_state.push_back(shader->osl_surface_ref);
og->surface_state.push_back(shader->osl_surface_ref);
}
else {
og->surface_state.push_back(shader->osl_surface_bump_ref);
}
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);
og->bump_state.push_back(shader->osl_surface_bump_ref);
} }
#else #else

View File

@@ -407,6 +407,8 @@ void ShaderManager::device_update_common(Device *device,
flag |= SD_VOLUME_CUBIC; flag |= SD_VOLUME_CUBIC;
if(shader->graph_bump) if(shader->graph_bump)
flag |= SD_HAS_BUMP; flag |= SD_HAS_BUMP;
if(shader->displacement_method != DISPLACE_BUMP)
flag |= SD_HAS_DISPLACEMENT;
/* shader with bump mapping */ /* shader with bump mapping */
if(shader->displacement_method != DISPLACE_TRUE && shader->graph_bump) if(shader->displacement_method != DISPLACE_TRUE && shader->graph_bump)

View File

@@ -146,9 +146,8 @@ int SVMCompiler::stack_size(SocketType::Type type)
return size; return size;
} }
int SVMCompiler::stack_find_offset(SocketType::Type type) int SVMCompiler::stack_find_offset(int size)
{ {
int size = stack_size(type);
int offset = -1; int offset = -1;
/* find free space in stack & mark as used */ /* find free space in stack & mark as used */
@@ -175,6 +174,11 @@ int SVMCompiler::stack_find_offset(SocketType::Type type)
return 0; return 0;
} }
int SVMCompiler::stack_find_offset(SocketType::Type type)
{
return stack_find_offset(stack_size(type));
}
void SVMCompiler::stack_clear_offset(SocketType::Type type, int offset) void SVMCompiler::stack_clear_offset(SocketType::Type type, int offset)
{ {
int size = stack_size(type); int size = stack_size(type);
@@ -647,6 +651,9 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty
case SHADER_TYPE_DISPLACEMENT: case SHADER_TYPE_DISPLACEMENT:
clin = node->input("Displacement"); clin = node->input("Displacement");
break; break;
case SHADER_TYPE_BUMP:
clin = node->input("Normal");
break;
default: default:
assert(0); assert(0);
break; break;
@@ -663,6 +670,13 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty
output->stack_offset = SVM_STACK_INVALID; output->stack_offset = SVM_STACK_INVALID;
} }
/* for the bump shader we need add a node to store the shader state */
int bump_state_offset = SVM_STACK_INVALID;
if(type == SHADER_TYPE_BUMP) {
bump_state_offset = stack_find_offset(SVM_BUMP_EVAL_STATE_SIZE);
add_node(NODE_ENTER_BUMP_EVAL, bump_state_offset);
}
if(shader->used) { if(shader->used) {
if(clin->link) { if(clin->link) {
bool generate = false; bool generate = false;
@@ -680,6 +694,9 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty
generate = true; generate = true;
shader->has_displacement = true; shader->has_displacement = true;
break; break;
case SHADER_TYPE_BUMP: /* generate bump shader */
generate = true;
break;
default: default:
break; break;
} }
@@ -696,13 +713,21 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty
node->compile(*this); node->compile(*this);
} }
/* add node to restore state after bump shader has finished */
if(type == SHADER_TYPE_BUMP) {
add_node(NODE_LEAVE_BUMP_EVAL, bump_state_offset);
}
/* if compile failed, generate empty shader */ /* if compile failed, generate empty shader */
if(compile_failed) { if(compile_failed) {
svm_nodes.clear(); svm_nodes.clear();
compile_failed = false; compile_failed = false;
} }
add_node(NODE_END, 0, 0, 0); /* for bump shaders we fall thru to the surface shader, but if this is any other kind of shader it ends here */
if(type != SHADER_TYPE_BUMP) {
add_node(NODE_END, 0, 0, 0);
}
} }
void SVMCompiler::compile(Scene *scene, void SVMCompiler::compile(Scene *scene,
@@ -752,17 +777,22 @@ void SVMCompiler::compile(Scene *scene,
shader->has_object_dependency = false; shader->has_object_dependency = false;
shader->has_integrator_dependency = false; shader->has_integrator_dependency = false;
/* generate surface shader */ /* generate bump shader */
if(shader->displacement_method == DISPLACE_TRUE || !shader->graph_bump) { if(shader->displacement_method != DISPLACE_TRUE && shader->graph_bump) {
scoped_timer timer((summary != NULL)? &summary->time_generate_surface: NULL); scoped_timer timer((summary != NULL)? &summary->time_generate_bump: NULL);
compile_type(shader, shader->graph, SHADER_TYPE_SURFACE); compile_type(shader, shader->graph_bump, SHADER_TYPE_BUMP);
global_svm_nodes[index].y = global_svm_nodes.size(); global_svm_nodes[index].y = global_svm_nodes.size();
global_svm_nodes.insert(global_svm_nodes.end(), svm_nodes.begin(), svm_nodes.end()); global_svm_nodes.insert(global_svm_nodes.end(), svm_nodes.begin(), svm_nodes.end());
} }
else {
scoped_timer timer((summary != NULL)? &summary->time_generate_bump: NULL); /* generate surface shader */
compile_type(shader, shader->graph_bump, SHADER_TYPE_SURFACE); {
global_svm_nodes[index].y = global_svm_nodes.size(); scoped_timer timer((summary != NULL)? &summary->time_generate_surface: NULL);
compile_type(shader, shader->graph, SHADER_TYPE_SURFACE);
/* only set jump offset if there's no bump shader, as the bump shader will fall thru to this one if it exists */
if(shader->displacement_method == DISPLACE_TRUE || !shader->graph_bump) {
global_svm_nodes[index].y = global_svm_nodes.size();
}
global_svm_nodes.insert(global_svm_nodes.end(), svm_nodes.begin(), svm_nodes.end()); global_svm_nodes.insert(global_svm_nodes.end(), svm_nodes.begin(), svm_nodes.end());
} }

View File

@@ -99,6 +99,7 @@ public:
int stack_assign(ShaderInput *input); int stack_assign(ShaderInput *input);
int stack_assign_if_linked(ShaderInput *input); int stack_assign_if_linked(ShaderInput *input);
int stack_assign_if_linked(ShaderOutput *output); int stack_assign_if_linked(ShaderOutput *output);
int stack_find_offset(int size);
int stack_find_offset(SocketType::Type type); int stack_find_offset(SocketType::Type type);
void stack_clear_offset(SocketType::Type type, int offset); void stack_clear_offset(SocketType::Type type, int offset);
void stack_link(ShaderInput *input, ShaderOutput *output); void stack_link(ShaderInput *input, ShaderOutput *output);