In Microsoft's example for how to use the PixelShader they use a singleton. I've seen the same pattern in other places, and here they say
The pixel shader is stored in a private static field _pixelShader. This field is static, because one instance of the compiled shader code is enough for the whole class.
We've seen several memory leak issues when using this pattern. The PixelShader is involved is event handling that don't always get cleared correctly. We had to freeze them, and saw some improvement. We had to manually do some detachments
// Attach/detach effect as UI element is loaded/unloaded. This avoids
// a memory leak in the shader code as described here:
element.Loaded += (obj, args) =>
{
effect.PixelShader = ms_shader;
element.Effect = effect;
};
element.Unloaded += (obj, args) =>
{
effect.PixelShader = null;
element.Effect = null;
};
And even now under stress there are still memory leaks in that area. Does anyone know if the PixelShader uses heavy resources worth the trouble with using a singleton?
Definitely NO. However, the reason is not with PixelShader
itself, but its usage in ShaderEffect
.
Almost all implementations of custom Shader Effects for WPF are based on Microsoft's .NET 3.5 samples, which were not updated for .NET 4.0. It was fine to use singleton PixelShader
instance for all effects, which supported only ps_2_0 shaders. With .NET 4.0 Microsoft introduced support for ps_3_0 pixel shaders (for GPU-accelerated devices), and with that, they introduced memory leak into ShaderEffect
class.ShaderEffect
tracks its PixelShader
property and checks that it doesn't use ps_3_0 registers for ps_2_0 bytecode by strong subscription to PixelShader's internal event called _shaderBytecodeChanged
. Therefore, singleton PixelShader
used by multiple ShaderEffect
instances serves as Domination Root for GC and retends all objects which were used with any instance of corresponding ShaderEffect
. That is known memory leak, which will not be fixed.
If you would like to use PixelShader
as singleton without leaks, then you will have to use runtime hacking: every time PixelShader
instance is assigned to PixelShader
property of ShaderEffect
class, _shaderBytecodeChanged
field of PixelShader
instance should be manually cleared, e.g.:
var ei = typeof(PixelShader).GetEvent("_shaderBytecodeChanged",
BindingFlags.Instance | BindingFlags.NonPublic);
var fi = typeof(PixelShader).GetField(ei.Name,
BindingFlags.Instance | BindingFlags.NonPublic);
fi.SetValue(pixelShader,null);
Of course, these operations can be optimized by generating helper methods in runtime by DynamicMethod
infrastructure or similar mechanisms. However, this should be used only for shaders which are definitely ps_2_0 or definitely ps_3_0
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With