0

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.

asked Oct 25 at 3:06
5
  • Shadow is a builtin Direct2D effect (learn.microsoft.com/en-us/windows/win32/direct2d/drop-shadow) so you don't need to register it, what's your question exactly? Commented Oct 25 at 7:56
  • To create another effect, for pixelation for example. Shadow was an example. Commented Oct 25 at 22:39
  • 1
    Yes you do have to register it. Everything is decribed here learn.microsoft.com/en-us/windows/win32/direct2d/custom-effects unfortunately SharpDx is not a 1 for 1 wrapper with DirectX technology, it's a bit too "smart" if you ask me (plus it's completely legacy now), so it's not easy to understand how you're supposed to use it when you start from official Direct2D doc. There's an old tutorial here advertboy.wordpress.com/2012/04/25/… Commented Oct 26 at 7:23
  • Oh thanks, I found a project on github with that RippleEffect and tried replicating, currently I'm stuck with a InvalidParams on the SetSingleTransformNode() 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? Commented Oct 26 at 17:20
  • @SimonMourier I made it work, I just need to fix my hlsl now. Thanks! Commented Oct 26 at 20:31

1 Answer 1

0

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);
 }
}
answered Oct 26 at 20:30
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.