TextureFX
TextureFX is a specification for nodes that do GPU/shader based texture operations. Shaders are written in SDSL which is a superset of HLSL. We distinguish Sources, Mixers, Filters and Utils.
Here is what you need to know to write your own:
Creating a new TextureFX
First, see Editing Shaders on setting up an external shader editing application.
Then follow Start from a Template for the quickest way to write a shader. If instead you want to create a shader file manually, here is what you need to take care of in order for the node factory to pick up a file and interpret it as a TextureFX shader:
- The file must be placed in a subfolder called
shaders
next to your .vl file - The shader name must be unique among the shaders shipping with vvvv and your own
- The shader name must end in _TextureFX
- The filename must be: [shader-name]_TextureFX.sdsl
Category and Aspects
By default, every TextureFX node will show up in the Stride\Textures
category. In order to move a node to a subcategory, use a node attribute.
Aspects, like "Experimental", "Internal", "Obsolete" and "Advanced" can be specified in two different ways:
- Either as part of the shaders filename, in which case you must not forget that the shader name itself must be identical to the filename
- Or as part of the category node attribute.
Base Shaders to inherit from
There are a bunch of shaders you can inherit useful functionality from. Multiple Inheritance is allowed!
- Shipping with Stride: Use the Shader Explorer to browse available shaders to inherit from (requires also Stride to be installed)
- Shipping with VL.Stride: Explore the .sdsl files in: C:\Program Files\vvvv\vvvv_gamma_...\lib\packs\VL.Stride.Runtime...\stride\Assets\Effects
Recommended base shaders
TextureFX
TextureFX derives from ImageEffectShader, SpriteBase, ShaderBase, Texturing and ShaderUtils.
FilterBase
Derives from TextureFX. Allows to you implement the Filter() function, which comes with the color of the input texture as parameter:
shader MyFx_TextureFX : FilterBase
{
float4 Filter(float4 tex0col)
{
tex0col.rgb = 1 - tex0col.rgb;
return tex0col;
}
};
Note
Using the tex0col
input is not mandatory and you can still add other texture inputs to sample from.
MixerBase
Derives from TextureFX. Allows you to implement the Mix() function, which comes with the colors of the two input textures and a fader parameter:
shader Mix_TextureFX : MixerBase
{
float4 Mix(float4 tex0col, float4 tex1col, float fader)
{
return lerp(tex0col, tex1col, fader);
}
};
Note
Using the tex0col
and tex1col
inputs is not mandatory and you can still add other texture inputs to sample from.
ShaderUtils
ShaderUtils defines constants like PI and gives access to many commonly used shader snippets.
Include Files
See Includes.
Node Attributes
Attributes allow you to configure your TextureFX node. Here is an example of some attributes applied to a shader:
[Category("Filter")]
[Summary("Description for what the filter does")]
[Remarks("Any special notes")]
[Tags("Space-separated list of tags")]
[OutputFormat("R8G8B8A8_UNorm_SRgb")]
shader MyFX_TextureFX : TextureFX
{
stage override float4 Shading()
{
return ColorUtilityTemp.LinearToSRgb(InTex0());
}
};
Attribute | Description |
---|---|
Category | If not specified, the node will show up under Stride\Textures . Specifying a category allows you to put the node in a subcategory from there. Use : to define a new root category (e.g. :My.Category ). Also aspects can be added among the category here, like e.g. Filter.Experimental |
Summary | A short info that shows up as tooltip on the node in the NodeBrowser and when hovered in a patch. |
Remarks | Additional info regarding the node visible on the tooltip in the patch. |
Tags | A list of search terms (separated by space, not comma!) the node should be found with, when typed in the NodeBrowser. |
OutputFormat | Allows to specify the outputs texture format. Valid Values: PixelFormats. If not specified, defaults to R8G8B8A8_UNorm_SRgb. |
WantsMips | Requests mipmaps for a specific texture input. See Mipmaps below. |
DontConvertToLinearOnRead | You'll most likely not need this flag! If set, disables the automatic sRGB-to-linear conversion that happens when reading (sampling) from an sRGB input texture. Only relevant if the input texture format has the _SRgb suffix and the pipeline is set to linear color space, which is the default. See sRGB and Linear Color Space below. |
DontConvertToSRgbOnWrite | You'll most likely not need this flag! If set, this flag disables the automatic linear-to-sRGB conversion that happens when writing the shader result into an sRGB texture. Only relevant if OutputFormat has the _SRgb suffix and the pipeline is set to linear color space, both of which is the default. See sRGB and Linear Color Space below. |
Source Node Attributes
The following attributes are specifically for use with Source TextureFX:
[TextureSource]
shader Foo_TextureFX : TextureFX
Attribute | Description |
---|---|
TextureSource | Specifies a shader to behave as a TextureFX Source. Also: Any Texture input pin will keep its name as declared (For Filters and Mixers this is not the case. There the pins are renamed to have concise namings across all nodes) |
Pin Attributes
Every pin definition can have the following Attributes:
Attribute | Description |
---|---|
Summary | A short info that shows up as tooltip on the pin |
Remarks | Additional info that shows up as tooltip on the pin |
Optional | Pins marked as optional don't show up on the node by default. They need to be activiated via the nodes configuration menu. |
Color | To have a float4 input show up as a color pin |
EnumType | To have an int input show up as an enum. NOTE: This also requires you to define the specified enum in C# and have it referenced by the .vl documents you're using the TextureFX in. |
Default | Only for Compute inputs to specify their default. For primitive inputs you can simply set the default with the variable definition. |
Examples
[Color]
[Summary("The color to do this and that")]
float4 MyColor;
[EnumType("VL.Stride.Effects.TextureFX.NoiseType")]
int Type;
[Default(1, 1, 1, 1)]
compose ComputeFloat4 Control;
Inputs
Every TextureFX node has exactly one texture output and a couple of inputs by default:
Sources
Name | Type | Optional | Description |
---|---|---|---|
Output Format | PixelFormat Enum | x | The format of the output texture, defaults to R8G8B8A8_UNorm_SRgb |
Output Size | Int2 | The size of the output texture | |
Enabled | Boolean | Whether or not the output is updated |
To make a TextureFX a "Source", specify the "TextureSource" attribute.
Filter, Mixer and Utils
Name | Type | Optional | Description |
---|---|---|---|
Input | Texture | ||
Sampler | SamplerState | x | Allows to override the default sampler |
Control | GPU |
Allows to blend between the input and the result of the operation | |
Output Format | PixelFormat Enum | x | Allows to override the format of the output texture, defaults to None , meaning the format of the input texture is used |
Output Size | Int2 | x | Allows to override the size of the output texture |
Output Texture | Texture | x | Allows to render the output in a given texture, rather than using nodes own texture |
Apply | Boolean | Whether the effect is applied to the input texuture, or the effect is bypassed and the input is returned unchanged |
Multiple passes
At this point there is no support for multiple passes in shader code. That said, you can still create multipass TextureFX by preparing the passes as individual TextureFX and then plugging them together in a patch. For an example, see how the Glow filter is done.
Note that for such cases it makes sense to mark the individual passes with the "Internal" aspect, because they probably are not meant to be used on their own and therefore should not show up in the NodeBrowser.
Mipmaps
Some effects need mipmaps for the input texture. This can be indicated via the [WantsMips("")]
attribute. It takes a comma separated list of texture variable names that need mipmaps. The TextureFX wrapper will then take care of generating the mipmaps, if the texture doesn't have them already. To save performance, an additional input pin is created that controls whether the mipmaps should be generated in every frame or only when the texture instance changes, the default is true
.
[WantsMips("Texture0, MyTexture, ...")]
shader Foo_TextureFX : TextureFX
sRGB and Linear Color Space
By default the rendering pipeline is set to linear color space. This is the correct color space for doing color math, such as blending and lighting. But almost all images are stored in non-linear sRGB color space because it allows for lower bit depths, hence smaller file sizes. To solve this, graphics APIs have low bit depth pixel formats with the _SRgb
suffix to indicate that the pixel values are in sRGB color space.
The (linear) graphics pipeline will automatically convert from sRGB to linear when a pixel is sampled from an sRGB texture and it will automatically convert from linear to sRGB when a pixel is written into an sRGB texture set as render target.
However, if you copy shader code that was written for an legacy sRGB/non-linear pipeline (as DX9/DX11 in vvvv beta were), you might want to indicate that the input and output colors are in sRGB space.
To do this, you can use two attributes that declare the read and write intent.
[DontConvertToLinearOnRead]
, input should stay as sRGB. This can involve an internal copy of the texture if the resource is not typeless (i.e. is strongly typed).[DontConvertToSRgbOnWrite]
, output is already sRGB.
[DontConvertToLinearOnRead] //could involve a copy for each input texture
[DontConvertToSRgbOnWrite] //almost cost free
shader MySRgbFX_TextureFX : FilterBase
{
float4 Filter(float4 tex0col)
{
tex0col.rgb = tex0col.rgb;
return tex0col;
}
};
These attributes will only do something if the input textures or render target have the _SRgb
suffix.
Because there can be more than one input texture, it is also possible to specify a comma separated list of variable names of input textures to set the input attribute only for specific ones:
[DontConvertToLinearOnRead("Texture0, MyTexture")]
[DontConvertToSRgbOnWrite]
shader MySRgbFX_TextureFX : FilterBase
System Values and Shader Semantics
If needed, HLSL shader semantics can be used.
Many of those are already available in more human-readable terms inherited via the ShaderBase.
Render Target Size
A common requirement is the size of the render target, this is provided via the ViewSize
variable. It describes the size of the current viewport, which is the full size of the render target for TextureFX:
float2 targetSize = ViewSize;
Time
The current time and the frame time diffrence can be obtained by inheriting from the Global shader and using the Time
and TimeStep
variables. The values are automatically set by the runtime.
shader MyBlinker_TextureFX : FilterBase, Global
{
float4 Filter(float4 tex0col)
{
var blink = frac(Time) > 0.5;
tex0col.rgb = tex0col.rgb * blink;
return tex0col;
}
};