I want to create a custom pixelation effect and apply to a DeviceContext:
using var shadowEffect = new SharpDX.Direct2D1.Effects.Shadow(DeviceContext);
shadowEffect.SetInput(0, shadowBitmap, false);
shadowEffect.BlurStandardDeviation = 10.0f;
DeviceContext.DrawImage(shadowEffect);
I know that I need to create a pixel shader (hlsl) and compile it using fxc/dxc, then I can use that effect in my render pipeline.
I just don't know how to use that effect, if I need to register some way.
What I have:
Pixel Shader:
Texture2D InputTexture : register(t0);
SamplerState InputSampler : register(s0);
cbuffer PixelateParams : register(b0)
{
float2 blockSize;
float2 textureSize;
float featherRadius;
};
float4 main(float2 uv : TEXCOORD) : SV_Target
{
float2 pixelCoord = uv * textureSize;
float2 blockOrigin = floor(pixelCoord / blockSize) * blockSize;
float2 sampleUV = blockOrigin / textureSize;
float4 color = InputTexture.Sample(InputSampler, sampleUV);
if (featherRadius > 0)
{
float2 distToEdge = min(pixelCoord, textureSize - pixelCoord);
float edgeDist = min(distToEdge.x, distToEdge.y);
float alpha = saturate(edgeDist / featherRadius);
color.a *= alpha;
}
return color;
}
Shader compilation:
fxc.exe /T ps_5_0 /E main /Fo $csoFile $hlslFile
//I also tried using ps_4_0, ps_4_0_level_9_1 and even dxc.exe, with no success.
Effect:
public class PixelateEffect : CustomEffectBase, DrawTransform
{
private static readonly Guid Identifier = new("AAAAF3D2-7322-4E9A-9F3F-12345678944B");
private DrawInformation? _drawInformation;
private PixelationParams _parameters;
[StructLayout(LayoutKind.Sequential)]
private struct PixelationParams
{
public Vector2 BlockSize; //8 bytes.
public Vector2 TextureSize; //8 bytes.
public float FeatherRadius; //4 bytes.
//Padding variables, so that the struct aligns to 16 bytes (20 + 12 = 32 (16 x 2))
private float Padding1; //4 bytes
private Vector2 Padding2; //8 bytes
}
public Vector2 PixelSize
{
get => _parameters.BlockSize;
set
{
_parameters.BlockSize = new Vector2(MathUtil.Clamp(value[0], 1f, 500f), MathUtil.Clamp(value[1], 1f, 500f));
UpdateConstants();
}
}
public Vector2 TextureSize
{
get => _parameters.TextureSize;
set
{
_parameters.TextureSize = new Vector2(MathUtil.Clamp(value[0], 1f, 10_000f), MathUtil.Clamp(value[1], 1f, 10_000f));
UpdateConstants();
}
}
public float FeatherRadius
{
get => _parameters.FeatherRadius;
set
{
_parameters.FeatherRadius = MathUtil.Clamp(value, 0f, 500f);
UpdateConstants();
}
}
public PixelateEffect()
{
_parameters = new PixelationParams
{
BlockSize = new Vector2(5F, 5F),
TextureSize = new Vector2(10f, 10f),
FeatherRadius = 10f
};
}
public override void Initialize(EffectContext effectContext, TransformGraph transformGraph)
{
var assembly = Assembly.GetAssembly(typeof(PixelateEffect));
const string resourceName = "MyApp.Shaders.Compiled.Pixelate.cso";
using (var stream = assembly!.GetManifestResourceStream(resourceName))
{
if (stream == null)
throw new FileNotFoundException("Could not find embedded shader resource.", resourceName);
using (var ms = new MemoryStream())
{
stream.CopyTo(ms);
effectContext.LoadPixelShader(Identifier, ms.ToArray());
}
}
//Failure happens here, after exiting the SetDrawInformation() method.
transformGraph.SetSingleTransformNode(this);
}
// Called by Direct2D when a property changes or right before Draw
public override void PrepareForRender(ChangeType changeType)
{
//When a property changes in the Effect wrapper, this is where we push the constant buffer update to the GPU.
UpdateConstants();
}
public override void SetGraph(TransformGraph transformGraph)
{
throw new NotImplementedException();
}
public void SetDrawInformation(DrawInformation drawInfo)
{
_drawInformation = drawInfo;
_drawInformation.SetPixelShader(Identifier, PixelOptions.None);
_drawInformation.SetInputDescription(0, new InputDescription(Filter.MinimumMagLinearMipPoint, 1));
//_drawInformation.SetPixelConstantBuffer(ref _parameters);
}
public RawRectangle MapInvalidRect(int inputIndex, RawRectangle invalidInputRect)
{
return invalidInputRect;
}
public RawRectangle MapInputRectanglesToOutputRectangle(RawRectangle[] inputRects, RawRectangle[] inputOpaqueSubRects, out RawRectangle outputOpaqueSubRect)
{
if (inputRects.Length != 1)
throw new ArgumentException("InputRects must be length of 1", nameof(inputRects));
outputOpaqueSubRect = default(Rectangle);
return inputRects[0];
}
public void MapOutputRectangleToInputRectangles(RawRectangle outputRect, RawRectangle[] inputRects)
{
var expansion = 0;
if (inputRects.Length != 1)
throw new ArgumentException("InputRects must be length of 1", nameof(inputRects));
inputRects[0].Left = outputRect.Left - expansion;
inputRects[0].Top = outputRect.Top - expansion;
inputRects[0].Right = outputRect.Right + expansion;
inputRects[0].Bottom = outputRect.Bottom + expansion;
}
public int InputCount => 1;
private void UpdateConstants()
{
_drawInformation?.SetPixelConstantBuffer(ref _parameters);
}
}
Usage:
//Factory2
Factory.RegisterEffect<PixelateEffect>();
var pixelateEffect = new SharpDX.Direct2D1.Effect<PixelateEffect>(DeviceContext);
pixelateEffect.SetValueByName("TextureSize", SharpDX.Direct2D1.PropertyType.Vector2, new SharpDX.Vector2(width, height));
pixelateEffect.SetInput(0, cropped, true);
When the PixelateEffect is initialized, it calls Initialize() -> SetSingleTransformNode() -> InputCount getter, then fails:
SharpDX.SharpDXException: HRESULT: [0x80070057], Module: [General], ApiCode: [E_INVALIDARG/Invalid Arguments], Message: The parameter is incorrect.
1 Answer 1
The issue was that the shader params must be decorated with the PropertyBinding atribute, which is the equivalent of having the XML descriptor used by less abstract libraries.
Now I just need to fix my shader as the pixelation code isn't doing anything.
/// <summary>
/// Constants used to set properties for Ripple custom effect.
/// </summary>
public enum PixelateProperties : int
{
BlockSize = 0,
TextureSize = 1,
FeatherRadius = 2
}
[CustomEffect("Adds a pixelate effect", "Stylize", "Nicke Manarin")]
[CustomEffectInput("Source")]
public class PixelateEffect : CustomEffectBase, DrawTransform
{
private static readonly Guid Identifier = new("AAAAF3D2-7322-4E9A-9F3F-12345678944B");
private DrawInformation? _drawInformation;
private PixelationParams _parameters;
[StructLayout(LayoutKind.Sequential)]
private struct PixelationParams
{
public Vector2 BlockSize; //8 bytes.
public Vector2 TextureSize; //8 bytes.
public float FeatherRadius; //4 bytes.
//Padding variables, so that the struct aligns to 16 bytes (20 + 12 = 32 (16 x 2))
private float Padding1; //4 bytes
private Vector2 Padding2; //8 bytes
}
[PropertyBinding((int)PixelateProperties.BlockSize, "(1.0, 1.0)", "(500.0, 500.0)", "(5.0, 5.0)")]
public Vector2 BlockSize
{
get => _parameters.BlockSize;
set
{
_parameters.BlockSize = new Vector2(MathUtil.Clamp(value[0], 1f, 500f), MathUtil.Clamp(value[1], 1f, 500f));
UpdateConstants();
}
}
[PropertyBinding((int)PixelateProperties.TextureSize, "(1.0, 1.0)", "(10000.0, 10000.0)", "(10.0, 10.0)")]
public Vector2 TextureSize
{
get => _parameters.TextureSize;
set
{
_parameters.TextureSize = new Vector2(MathUtil.Clamp(value[0], 1f, 10_000f), MathUtil.Clamp(value[1], 1f, 10_000f));
UpdateConstants();
}
}
[PropertyBinding((int)PixelateProperties.FeatherRadius, "0.0", "1000.0", "10.0")]
public float FeatherRadius
{
get => _parameters.FeatherRadius;
set
{
_parameters.FeatherRadius = MathUtil.Clamp(value, 0f, 500f);
UpdateConstants();
}
}
public PixelateEffect()
{
_parameters = new PixelationParams
{
BlockSize = new Vector2(5F, 5F),
TextureSize = new Vector2(10f, 10f),
FeatherRadius = 10f
};
}
public override void Initialize(EffectContext effectContext, TransformGraph transformGraph)
{
var assembly = Assembly.GetAssembly(typeof(PixelateEffect));
const string resourceName = "NStudio.Shaders.Compiled.Pixelate.cso";
using (var stream = assembly!.GetManifestResourceStream(resourceName))
{
if (stream == null)
throw new FileNotFoundException("Could not find embedded shader resource.", resourceName);
using (var ms = new MemoryStream())
{
stream.CopyTo(ms);
effectContext.LoadPixelShader(Identifier, ms.ToArray());
}
}
transformGraph.SetSingleTransformNode(this);
}
// Called by Direct2D when a property changes or right before Draw
public override void PrepareForRender(ChangeType changeType)
{
//When a property changes in the Effect wrapper, this is where we push the constant buffer update to the GPU.
UpdateConstants();
}
public override void SetGraph(TransformGraph transformGraph)
{
throw new NotImplementedException();
}
public void SetDrawInformation(DrawInformation drawInfo)
{
_drawInformation = drawInfo;
_drawInformation.SetPixelShader(Identifier, PixelOptions.None);
_drawInformation.SetInputDescription(0, new InputDescription(Filter.MinimumMagLinearMipPoint, 1));
}
public RawRectangle MapInvalidRect(int inputIndex, RawRectangle invalidInputRect)
{
return invalidInputRect;
}
public RawRectangle MapInputRectanglesToOutputRectangle(RawRectangle[] inputRects, RawRectangle[] inputOpaqueSubRects, out RawRectangle outputOpaqueSubRect)
{
if (inputRects.Length != 1)
throw new ArgumentException("InputRects must be length of 1", nameof(inputRects));
outputOpaqueSubRect = default(Rectangle);
return inputRects[0];
}
public void MapOutputRectangleToInputRectangles(RawRectangle outputRect, RawRectangle[] inputRects)
{
if (inputRects.Length != 1)
throw new ArgumentException("InputRects must be length of 1", nameof(inputRects));
inputRects[0].Left = outputRect.Left;
inputRects[0].Top = outputRect.Top;
inputRects[0].Right = outputRect.Right;
inputRects[0].Bottom = outputRect.Bottom;
}
public int InputCount => 1;
private void UpdateConstants()
{
_drawInformation?.SetPixelConstantBuffer(ref _parameters);
}
}
Shadowwas an example.RippleEffectand tried replicating, currently I'm stuck with a InvalidParams on theSetSingleTransformNode()call inside the effect, when instantiating that effect. I'll read the tutorial to try again. What would you use instead of SharpDX for WPF?