Skip to content

Nesting multiple effects inside an effect

MWstudios edited this page Sep 10, 2021 · 5 revisions

Shaders in Direct2D work like shaders in Blender: each has a node network inside, although Direct2D's nodes are more hidden and harder to set up if you don't know what you're doing. But I suppose that applies to Blender nodes as well. According to Microsoft, using nested effects is faster than rendering one after another via Effect.SetInputEffect(), however I haven't tested that out. Nonetheless, you should still be concerned about this feature if you're using a lot of pixel shaders and experiencing performance issues.

How to do it

We'll need to override several methods from PVShaderBase, because nested effects work a bit differently than usual ones. They aren't hidden behind Effect<T>, instead you're using the effect classes themselves. That brings some good news and some bad news: The good news is, you can edit constant buffers directly, no need to use the scuffed Effect.SetValue(). The bad news is, their methods are never called automatically, since that would require using Effect<T> which we're not doing here. And that's why we'll be overriding a lot of methods - we need to call their methods along with our parent effect's.

In this example, we'll look at two nested effects inside an effect. For simplicity's sake, they're going to be CloneablePixelShaders (but other classes will work as well). This is how you do it:

public class ShaderNest : PVShaderBase
{
	private readonly CloneablePixelShader shader1, shader2;
	
	public TwoLinkedEffects() : base(sGuid)
	{
		shader1 = new(); shader2 = new();
	}
	
	static Guid sGuid = Guid.NewGuid();

	public override void Initialize(EffectContext eC, TransformGraph graph)
	{
		Global.ShaderFile = "shader1.cso"; // set shader file
		shader1.Initialize(eC, graph);     // initialize shader

		Global.ShaderFile = "shader2.cso";
		shader2.Initialize(eC, graph);

		base.Initialize(eC, graph);        // also initialize itself
	}

	public override void SetGraph(TransformGraph transformGraph)
	{
		transformGraph.Clear();
		transformGraph.AddNode(shader1);
		transformGraph.AddNode(shader2);
                                                                    // +----------------ShaderNest----------------+
		transformGraph.ConnectToEffectInput(0, shader1, 0); // |        +---------+    +---------+        |
		transformGraph.ConnectNode(shader1, shader2, 0);    // O IN0 -> O shader1 O -> O shader2 O -> OUT O
		transformGraph.SetOutputNode(shader2);              // |        +---------+    +---------+        |
	}                                                           // +------------------------------------------+
}

If you need to change a constant buffer inside the nested effects, override the PrepareForRender() method. (Assuming you've set up a constant buffer in the other class.)

public override void PrepareForRender(ChangeType changeType)
{
	shader1.SomeProperty = 1.0f;
	shader1.UpdateConstants();
}

How does it work?

Again, aside from that one article on Microsoft Docs, there is no other mention of this feature on the internet. It took me some time to figure out how exactly this should be done but here is the final result. The way it's done here more or less doesn't differentiate from the original C++ implementation (aside from the fact that we're overriding PVShaderBase).

In this wiki you can find out all the information on how to use the Ensoftener library. For more information on how to add Ensoftener to your project, see "Installing and running". The rest is dedicated to the library's features.

Notice: This wiki shows information for Ensoftener 5.0 and is currently outdated.

Clone this wiki locally