Question

I've come across strange behavior of pixel shader in WPF.

This problem is 100% reproducible, so I wrote small demo program. You can download source code here.

The root of all evil is tiny class titled MyFrameworkElement:

internal sealed class MyFrameworkElement : FrameworkElement
{
    public double EndX
    {
        get
        {
            return (double)this.GetValue(MyFrameworkElement.EndXProperty);
        }
        set
        {
            this.SetValue(MyFrameworkElement.EndXProperty, value);
        }
    }

    public static readonly DependencyProperty EndXProperty =
        DependencyProperty.Register("EndX",
            typeof(double),
            typeof(MyFrameworkElement),
            new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender));

    protected override void OnRender(DrawingContext dc)
    {
        dc.DrawLine(new Pen(Brushes.Red, 2), new Point(0, 0), new Point(this.EndX, 100));
        dc.DrawLine(new Pen(Brushes.Green, 3), new Point(10, 300), new Point(200, 10));
    }
}

As you can see this framework element renders 2 lines: lower line has permanent coordinates but upper line depends on EndX dependency property.

So this framework element is target for pixel shader effect. For simplicity's sake I use grayscale shader effect found here. So I applied GrayscaleEffect to MyFrameworkElement. You can see result, it looks nice.

Until I increase EndX property drastically.

Small line is blurred and big line is fine!

But if I remove grayscale effect, all lines will look as they should.

Can anybody explain what's the reason of this blurring? Or even better how can I solve this problem?

Was it helpful?

Solution

With a custom pixel shader it has to create an Intermediate Bitmap and then that texture gets sampled by the pixel shader.

You're creating a massive rendering, so your hitting some limitation in the render path.

A quick fix is to clip what you want rendered as follows:

Geometry clip = new RectangleGeometry(new Rect(0,0,this.ActualWidth, this.ActualHeight));

dc.PushClip(clip);

dc.DrawLine(new Pen(Brushes.Red, 2), new Point(0, 0), new Point(this.EndX, 100));
dc.DrawLine(new Pen(Brushes.Green, 3), new Point(200, 10), new Point(10, 300));

dc.Pop();

UPDATE:

One theory is that it's using a filter to scale the bitmap when it exceeds the maximum texture size (which can vary depending on your graphics card architecture)...so it goes through the pixel shader at a different size....then it gets scaled back to original size.

Thus the scaling filter is causing artifacts depending on the content of your bitmap (i.e. horizontal lines and vertical lines survive a scale down and up better than diagonal lines).

.NET 4 changed the default filter it uses for filtering to a lowerquality one...Bilinear, instead of Fant...maybe this impacts the quality that you get too.

http://10rem.net/blog/2010/05/16/more-on-image-resizing-in-net-4-vs-net-35sp1-bilinear-vs-fant

UPDATE2:

This kind of confirms what I was thinking above.

If you use the Windows Performance Toolkit/Suite (part of Windows SDK), then you can see the Video Memory being gobbled up in the orange graph while you increase the slider value because a bigger Intermediate Bitmap texture is being created. It keeps increasing until it hits a limit, then it flatlines...and thats when the pixelation becomes evident.

enter image description here

UPDATE3:

If you set the render mode to the "Software Renderer" (Tier 0) then you can see how it copes with rendering such a large visual - the artifacts start appearing at a different point....presumably because the texture size limit is larger/different to your GPUs. But the artifacts still appear because it's using a Bilinear filter internally.

Trying to use RenderOptions.SetBitmapScalingMode to up the filter to Fant doesn't seem to change the rendering quality in any way (I guess because it isn't honoured when it goes through the custom pixel shader path).

Put this in Application_Startup to see the software renderer results:

RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;

OTHER TIPS

Note that image is normally blurred in vertical direction, but is jagge in horizontal.

Since shaders are applied to raster images, not vector, the lines are rasterized into texture. Hardware usually supports textures up to 8198*8192. In my case the "blurring", as you call it, appears at slider value of 16384. So, my virtualBox virtual graphics card supports up to 16384*16384. Your limit may differ. So just keep this value lower than that.

But it's strange that WPF rasterizes whole image, since only small part of it visible. So there is also another possible reason, that lies inside shader itself, but it is compiled into binary, so i can't check it.

Update: In my case it looks this way: with shader applied

Looks like it is filtered vertically but not horizontally.

Ok, I've got this! I decompiled the library with your grayscale effect and also decompiled WCF PresentationCore library to check why BlurEffect works perfect in the same situation. And i found that BlurEffect implements abstract method Effect.GetRenderBounds which is absent in GrayscaleEffect. I also noticed that GrayscaleEffect is built against PresentationCore v 3.0.0 where Effect does not have GetRenderBound.

So this is an incompatibility between 3rd and 4th versions of WPF. There are three ways to fix it:

  1. If you have source code of GrayscaleEffect - add needed methods and compile it against 4.0.0 version of runtime.
  2. You can switch the runtime your application use to version 3.*.
  3. If you don't have sources of GrayscaleEffect but can't use 3rd version of runtime, write wrapper for GrayscaleEffect that inherits Effect (v4) and implements absent methods.

I tried 2nd way and the problem disappeared.

old question, but might be useful for someone having problem with blurring of image after applying Custom ShaderEffect.

Also problem OP mentioned might be releated to scale of rendered content, I had similar problem with blurring after applying ShaderEffects from WPFShadersLibrary to video, text and any other content within normal window.

What I noticed that that image shifts down by a tiny bit, resulting in "pixel splitting", so I created two new properties for chosen ShaderEffect : XOffset and YOffset, and applied them in HLSL (see code below), then binded to Sliders in XAML :

float2 newPos;
newPos.x = uv.x + offsetX;
newPos.y = uv.y + offsetY;

Then I experimented with some arbitrary offsets and was able to re-align the picture. There is still some minimal blurring (or loss in detail) but result was noticeably better.

Problem with this solution currently, that I don't know how to predict offset either depending on resolution or window size.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top