wpf icon indicating copy to clipboard operation
wpf copied to clipboard

Shader Effect broken at large sizes?

Open petsuter opened this issue 7 months ago • 12 comments

Description

Use a ShaderEffect similar to the example from the API docs. Use it on a large visual (e.g. a large rectangle or image.) Problem: Not the entire visual is drawn, if any effect is applied. (It does not matter what the effect does.)

Reproduction Steps

(Or use the attached full project below.)

With this C# code:

    public class CustomElement : FrameworkElement
    {
        public VisualCollection visuals;

        public CustomElement()
        {
            this.visuals = new VisualCollection(this);

            // Bug only manifests at a certain size:
            var rect = new Rect() { X = 300, Y = -3000, Width = 3400, Height = 3400 };

            void _DrawRectWithEffect(Rect rect, Effect? effect)
            {
                var visual = new DrawingVisual() { Effect = effect };
                using (var drawingContext = visual.RenderOpen())
                {
                    drawingContext.DrawRectangle(Brushes.Red, pen: null, rect);
                }
                this.visuals.Add(visual);
            }

            // Works correctly: red
            _DrawRectWithEffect(rect, effect: null);

            var effect = new TestEffect();

            // Bug: Only partially drawn: magenta
            _DrawRectWithEffect(rect, effect: effect);
        }

        protected override int VisualChildrenCount => visuals.Count;
        protected override Visual GetVisualChild(int index) =>  visuals[index];
        public int Count => VisualChildrenCount;
        public Visual this[int index] => GetVisualChild(index);
    }

    public class TestEffect : ShaderEffect
    {
        private static Uri _MakePackUri(string relativeFile)
        {
            var assembly = typeof(TestEffect).Assembly;
            var assemblyShortName = assembly.ToString().Split(',')[0];
            var uriString = $"pack://application:,,,/{assemblyShortName};component/{relativeFile}";
            return new Uri(uriString);
        }

        public TestEffect()
        {
            PixelShader = new PixelShader() { UriSource = _MakePackUri("TestEffect.fx.ps") };
            UpdateShaderValue(InputProperty);
        }

        public static readonly DependencyProperty InputProperty = RegisterPixelShaderSamplerProperty("Input", typeof(TestEffect), 0);
        public Brush Input { get => (Brush)GetValue(InputProperty); set => SetValue(InputProperty, value); }
    }

And this trivial pixel shader file TestEffect.ps:

// Build shader with fxc.exe:
// "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\fxc.exe" /O0 /Fc /Zi /T ps_2_0 /Fo TestEffect.fx.ps TestEffect.ps

sampler2D implicitInput : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR
{
    return float4(1.0, 0.0, 1.0, 1.0); // Fixed color magenta #FF00FF
}

And this trivial XAML file MainWindow.xaml:

<Window x:Class="WpfShaderTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfShaderTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        WindowState="Maximized">
    <local:CustomElement />
</Window>

Compile and run it.

Expected behavior

The two visuals are drawn with the same rectangle coordinates, so they should cover the same area.

The second (magenta) visual with the effect should be positioned correctly and fully / exactly cover the first (red) visual.

Actual behavior

The first (red) rectangle (without effect) is positioned correctly. The second (magenta) rectangle (with effect) is positioned slightly incorrectly, so the red rectangle is not fully covered.

Regression?

Not sure.

Known Workarounds

None.

Impact

The real use case is an image viewer for very large images with deep zoom levels. This bug also happens when drawing images, not only when drawing rectangles. The image is then not completely displayed! Some parts are just missing! And with larger sizes / deeper zoom levels more and more of the image is missing.

So the impact is that it's impossible to rely on WPF for drawing large images with an effect applied. The effects are required to perform interactive image manipulations (thresholding, color space conversion, ...)

Configuration

.NET 8 Windows 11 x64 I assume this is not specific to that configuration.

Other information

None

petsuter avatar Jul 12 '24 13:07 petsuter