Composition
Beginner Programmer
In addition to the inheritance system, SDSL introduces the concept of composition. A composition is a member whose type is another shader class. It's defined the same way as variables.
You can compose with an instance of the desired shader class or an instance of a shader class that inherits from the desired one.
Example code
shader CompositionBase
{
float4 Compute()
{
return float4(0.0);
}
};
shader CompositionShaderA : CompositionBase
{
float4 myColor;
override float4 Compute()
{
return myColor;
}
};
shader CompositionShaderB : CompositionBase
{
float4 myColor;
override float4 Compute()
{
return 0.5 * myColor;
}
};
shader BaseShader
{
CompositionBase Comp0;
CompositionBase Comp1;
float4 GetColor()
{
return Comp0.Compute() + Comp1.Compute();
}
};
The compositions are compiled in their own context, meaning that the non-stage variables are only accessible within the composition. It's also possible to have compositions inside compositions.
Example code: access root context
If you want to access the root compilation context, you can use the following format:
shader CompositionShaderC : CompositionBase
{
BaseShader rootShader = stage;
float4 GetColor()
{
return rootShader.GetColor();
}
};
This is error-prone, since CompositionShaderC
expects BaseShader
to be available in the root context.
Example code: array of compositions
You can also create an array of compositions the same way you use an array of values. Since there's no way to know beforehand how many compositions there are, you should iterate using a foreach
statement.
shader BaseShaderArray
{
CompositionBase Comps[];
float4 GetColor()
{
float4 resultColor = float4(0.0);
foreach (var comp in Comps)
{
resultColor += comp.Compute();
}
return resultColor;
}
};
Example code: stage behavior
The behavior of the stage
keyword is straightforward: only one instance of the variable or method is produced.
shader BaseShader
{
stage float BaseStageValue;
float NonStageValue;
};
shader TestShader : BaseShader
{
BaseShader comp0;
BaseShader comp1;
};
// resulting shader (representation)
shader TestShader
{
float BaseStageValue;
float NonStageValue;
float comp0_NonStageValue;
float comp1_NonStageValue;
};
Example code: stage member behavior
shader BaseShader
{
stage float BaseStageMethod()
{
return 1.0;
}
float NonStageMethod()
{
return 2.0;
}
};
shader TestShader : BaseShader
{
BaseShader comp0;
BaseShader comp1;
};
// resulting shader (representation)
shader TestClass
{
float BaseStageMethod()
{
return 1.0;
}
float NonStageMethod()
{
return 2.0;
}
float comp0_NonStageMethod()
{
return 2.0;
}
float comp1_NonStageMethod()
{
return 2.0;
}
};
Keep in mind that even in composition, you can call for base methods, override them, and so on. Overriding happens in the same order as the compositions.
This behavior is useful when you need a value in multiple composition but you only need to compute it once (eg the normal in view space).
Clone behavior
The clone
keyword has a less trivial behavior. It prevents the stage
keyword to produce a unique method.
shader BaseShader
{
stage float BaseStageMethod()
{
return 1.0;
}
stage float BaseStageMethodNotCloned()
{
return 1.0;
}
};
shader CompShader : BaseShader
{
override clone float BaseStageMethod()
{
return 1.0 + base.BaseStageMethod();
}
override float BaseStageMethodNotCloned()
{
return 1.0f + base.BaseStageMethodNotCloned();
}
};
shader TestShader : BaseShader
{
CompShader comp0;
CompShadercomp1;
};
// resulting shader (representation)
shader TestShader
{
// cloned method
float base_BaseStageMethod()
{
return 1.0;
}
float comp0_BaseStageMethod()
{
return 1.0 + base_BaseStageMethod();
}
float BaseStageMethod() // in fact comp1_BaseStageMethod
{
return 1.0 + comp0_BaseStageMethod; // 3.0f
}
// not cloned method
float base_BaseStageMethodNotCloned()
{
return 1.0f;
}
float BaseStageMethodNotCloned()
{
return 1.0f + base_BaseStageMethodNotCloned(); // 2.0f
}
};
This behavior is useful when you want to repeat a simple function but with different parameters (eg adding color on top of another).