dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
6.93k stars 1.15k forks source link

Artifacts when using BitmapCacheBrush #9161

Open znakeeye opened 1 month ago

znakeeye commented 1 month ago

Description

Using BitmapCacheBrush you can singificantly improve rendering performance of complex shapes. However, for some shapes, you get undesired artifacts. Please see attached sample project.

It seems pixels leak from the edges. image

With some experience from the OpenGL world, I'd guess this has something to do with texture repeat mode. For our use case, we certainly don't want the brush to repeat. All in all, this looks like a WPF rendering bug.

Reproduction Steps

See attached sample project. Run it, and observe the artifacts.

WpfBitmapCacheBrushBug.zip

Expected behavior

Rendering a shape using BitmapCacheBrush should not cause pixels to leak to the other side of the image.

Actual behavior

BitmapCacheBrush causes visual artifacts.

Regression?

No response

Known Workarounds

No response

Impact

The BitmapCacheBrush is a powerful feature which allows you to cache and quickly render e.g. complex vector images. We successfully integrated it with our heavy Enterprise desktop app, allowing for unprecedented rendering performance in our data grid.

Having these visual artifacts is a bit of a bummer. There is no other way to get the same rendering performance for our vector images.

Configuration

Other information

No response

huiliuss commented 1 month ago
  1. In MainViewModel, set it

bitmapCache.SnapsToDevicePixels = true;

2.In MyShape, adjust Scale as follows:


public static double Scale = 20.0d;

3.In the MainWindow.xaml:


 <StackPanel Orientation="Horizontal">
     <local:MyShape Width="160"
                    Height="160"
                    Margin="20"
                    UseLayoutRounding="true"
                    Fill="{Binding MyIconBrush}"/>
     <Border>
         <Border x:Name="secondBorder" Background="White" CornerRadius="80" BorderThickness="10" BorderBrush="White">
             <local:MyShape Width="160"
            Height="160"
            Margin="20"
            UseLayoutRounding="true"            
            Fill="{Binding MySecondIconBrush}"/>
         </Border>
     </Border>
 </StackPanel>
znakeeye commented 1 month ago
  1. In MainViewModel, set it
bitmapCache.SnapsToDevicePixels = true;

2.In MyShape, adjust Scale as follows:

public static double Scale = 20.0d;

3.In the MainWindow.xaml:

 <StackPanel Orientation="Horizontal">
     <local:MyShape Width="160"
                    Height="160"
                    Margin="20"
                    UseLayoutRounding="true"
                    Fill="{Binding MyIconBrush}"/>
     <Border>
         <Border x:Name="secondBorder" Background="White" CornerRadius="80" BorderThickness="10" BorderBrush="White">
             <local:MyShape Width="160"
            Height="160"
            Margin="20"
            UseLayoutRounding="true"            
            Fill="{Binding MySecondIconBrush}"/>
         </Border>
     </Border>
 </StackPanel>

The sample project illustrates one case where artifacts are seen. Your modified code illustrates one case where artifacts are not seen.

Obviously our application is much more complex. For starters, we have these requirements:

The documentation states that BitmapCacheBrush is "useful when you need to paint complex content onto multiple elements". It doesn't say anything about unsupported layouts/setups.

huiliuss commented 1 month ago

1.After testing, you need at least UseLayoutRounding="True" to set up Scale = 20.0d

Build on your initial code


    <StackPanel Orientation="Horizontal">

        <local:MyShape

                       Width="160"

                       Height="160"

                       Margin="20"

                       UseLayoutRounding="True"

                       Fill="{Binding MyIconBrush}"/>

        <local:MyShape

               Width="160"

               Height="160"

               Margin="20"

               UseLayoutRounding="True"

               Fill="{Binding MySecondIconBrush}"/>

    </StackPanel>

public static double Scale = 20.0d;

2.I can't use SnapsToDevicePixels instead of UseLayoutRounding in case you use Fill.Related Docs (FrameworkElement.UseLayoutRounding Property (System.Windows) | Microsoft Learn)