Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is WPF rendering my controls with a diagonal split?

Tags:

wpf

xaml

WPF appears to be splitting my controls diagonally. What am I doing wrong? The problem is clearer on the blue button, but still noticeable on the one on the right.

alt textalt text

Bizarrely, kaxaml renders the buttons correctly. The WPF designer never seems to. When I run the sample code in a standalone 'WpfApplication1' it renders correctly. However, inside my application I get the diagonal 'cut'. It's probably worth mentioning that at runtime WPF doesn't seem to consistently apply the slicing effect, sometimes they render properly!

Update 1: A Clue! It is only affecting buttons! When I created a standalone Border and put a label as its content, no slicing effect?!?!! So it's no the xaml per se, but something magical to do with buttons?! Thinking out loud, something to do with not completely overriding the default Button template?

Update 2: Well, this is getting even more odd by the minute. It's something to do with the shadow effect. It seems that the first type of control to be drawn which has an effect (BitmapEffect or wpf 4.0 Effect), is split as are all other instances of control of that type. For example, here is a DatePicker with a lovely red shadow, which splits the DatePicker control diagonally, the button afterwards is fine despite also having an effect applied to it.

alt text

If I don't apply an effect, no controls are split. If I draw a control with an effect, this control is split and subsequent controls of different types are fine. However, if you have two or three controls of the same type, they get split too. Ie, if a button is split, all buttons on the page will also be split.

This must be something to do with my GPU or graphics drivers. I've updated them this morning but no joy. (I'm using Radeon Mobility HD 5650, v 8.683.2.0). If this problem is isolated to just my PC, I suppose it's not the end of the world. Possibly, I can beat it at it's own game by drawing a transparent effect on a random Path pixel at the top of each page or something...

Update 3

Oh dear. I have reproduced this on another PC now, so its not my graphics card or drivers.

        <StackPanel.Resources>

            <!-- Background for button when IsDefault  true"-->
            <LinearGradientBrush x:Key="DefaultButtonFill" StartPoint="0.5, 0" EndPoint="0.5, 1">
                <GradientStop Color="#FFDFEDEC" Offset="0"/>
                <GradientStop Color="#FF92B1E3" Offset="0.4"/>
                <GradientStop Color="#FF749EE0" Offset="0.5"/>
                <GradientStop Color="#94DDF6" Offset="1"/>
            </LinearGradientBrush>

            <!-- default button background -->
            <LinearGradientBrush x:Key="NotDefaultButtonFill" StartPoint="0.5, 0" EndPoint="0.5, 1">
                <GradientStop Color="#FFEFEFF5" Offset="0"/>
                <GradientStop Color="#FFE1E1E6" Offset="0.4"/>
                <GradientStop Color="#FFC7CBD0" Offset="0.5"/>
                <GradientStop Color="#FFE8ECEE" Offset="1"/>
            </LinearGradientBrush>

            <Style TargetType="Button">
                <Setter Property="FontSize" Value="14"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Border x:Name="border"
                        CornerRadius="12"
                        Background="{StaticResource NotDefaultButtonFill}"
                        Padding="25 8"
                        Margin="10"
                        Cursor="Hand">


                                <Border.BitmapEffect>
                                    <DropShadowBitmapEffect x:Name="shadow" Direction="280" Color="Black"  ShadowDepth="2" Opacity=".6"/>
                                </Border.BitmapEffect>

                                <!--
                    <Border.Effect>
                        <DropShadowEffect x:Name="shadow" Direction="280" Color="Black"  ShadowDepth="2" Opacity=".6"/>
                    </Border.Effect>-->

                                <ContentPresenter 
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Content="{TemplateBinding Content}" />

                            </Border>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsDefault" Value="True">
                                    <Setter TargetName="border" Property="Background" Value="{StaticResource DefaultButtonFill}"/>
                                </Trigger>
                                <Trigger Property="IsEnabled" Value="False">
                                    <Setter TargetName="border" Property="Background" Value="LightGray"/>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>





        </StackPanel.Resources>

        <StackPanel Orientation="Horizontal">
            <Button>Normal</Button>
            <Button IsDefault="True">IsDefault</Button>
        </StackPanel>
    </StackPanel>
like image 977
Noel Kennedy Avatar asked Jan 14 '11 20:01

Noel Kennedy


2 Answers

This is due to a bug caused by some subtle interaction between WPF and ATI graphics drivers. It seems to happen when using DropShadowEffect on elements that aren't quite positioned on pixel boundaries.

If you set UseLayoutRounding="True" on the elements in question (or in the appropriate style) that seems to fix the problem in a lot of cases, though not all. Another thing to do is to make sure you are not deliberately positioning elements at half-pixels (by using fractional margins, for example - this can happen if you move elements around in Expression Blend).

Here's a simple repro case:

 <UserControl
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <UserControl.Resources>
     <Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
         <Setter Property="Width" Value="28"/>
         <Setter Property="Height" Value="28"/>
         <Setter Property="Template">
             <Setter.Value>
                 <ControlTemplate TargetType="{x:Type Button}">
                     <Border x:Name="Border" Background="Transparent">
                         <Grid Margin="{TemplateBinding Padding}">
                         <ContentPresenter x:Name="contentPresenter" VerticalAlignment="Center"
                                           HorizontalAlignment="Center">
                           <ContentPresenter.Effect>
                             <DropShadowEffect BlurRadius="13" Color="Black" Opacity="1" ShadowDepth="0" Direction="0"/>
                           </ContentPresenter.Effect>
                         </ContentPresenter>
                         </Grid>
                     </Border>
                 </ControlTemplate>
             </Setter.Value>
         </Setter>
     </Style>
   </UserControl.Resources>
   <Grid>  
     <Button x:Name="MinimizeButton"
                Style="{StaticResource ButtonStyle}">
            <Rectangle x:Name="MinimizeIcon"
                       Width="11"
                       Height="2"
                       HorizontalAlignment="Center"
                       Margin="0,7,0,0"
                       VerticalAlignment="Bottom"
                       Stroke="Gray"
                       StrokeThickness="2"/>
    </Button>
   </Grid>
 </UserControl>

If you stick that in Kaxaml and move the zoom slider about, you should see the split appear when the zoom is set to 150.

If you add <Setter Property="UseLayoutRounding" Value="True"/> to the Button Style, zoom level 150 renders correctly, but the split does appear at zoom level 300.

Which is why I say my work-around doesn't work in all cases - if you only need to display your content at one fixed size, UseLayoutRounding might hide the problem at that size, but if you have a zoom feature in your app, you might still get the problem at times.

From the Microsoft Forums, here's what ATI had to say about the issue:

The root cause is that WPF 4.0 will use DX10 style texture coordinates addressing to render thetext with point filter (Round to nearest) while using DX9 API. We are followingthe DX9 rule (Truncate to nearest) as this is DX9 driver. If we force our HW touse DX10 style texture coordinate usage, the issue will disappear. However, Ithink Microsoft should follow their own rule, use DX9 style texture coordinateaddressing while using DX9 API."

If you can reproduce this issue too, go and add your vote to the issue I've added on Connect so that we can get this fixed - though I don't hold much hope; Microsoft repsonded to a similar issue by saying that they considered the work-around acceptable for the time being.

like image 111
Samuel Jack Avatar answered Oct 04 '22 15:10

Samuel Jack


The core problem occurs when WPF renders an effect to a tree that is at a partial-pixel offset. There are two basic approaches we could have done for the hardware path:

1) Create an intermediate texture that corresponds to screen pixels and render the tree at a partial-pixel offset within that texture. Then when transfering that intermediate back to the screen, we can use pixel-aligned quads and use simple nearest-neighbor sampling since the content is pixel-aligned.

2) Create an intermediate texture that corresponds to the bounds of the tree and render the tree at a 0 offset within that texture. Then when transfering the intermediate back to the screen, we need to use a quad that covers partial pixels. Since the quad could be covering partial pixels, the sampling mode is important. Sampling modes like NearestNeighbor can cause aliasing artifacts that could be GPU dependent.

WPF chose #2 and forced nearest-neighbor sampling - unless the tree that had any transform other than offset or scale (most commonly, a rotation) in which case it forced bilinear sampling. This is subtly different than the software rasterization path, which would use the interpolation mode in effect for the tree, unless the tree had a transform other than offset or scale, in which case it also forced bilinear sampling.

We are "fixing" this issue by bringing the hardware path into agreement with the software path. This means we will respect the sampling mode in effect for the tree being rendered. This can be set from managed code with the RenderOptions.BitmapScalingMode attached property. The default value is Linear, which is a more accommodating sampling mode when the quad covers partial pixels. However, it does introduce a noticeable blurriness - but the same as the software rasterization produces.

For 4.0 there are a couple of workarounds:

1) Prevent the element tree from being positioned at a partial pixel. For example, you can set the UseLayoutRounding property to true on containers that position the element with the effect.

2) Apply a small rotation to cause the rendering path to use bilinear interpolation. This can be very small, such as a RenderTransform with a <RotateTransform Angle="0.00000000001"/>

Note that custom ShaderEffects use explicit samplers with configurable sampling modes.

Customers will be able to get current behavior by setting the RenderOptions.BitmapScalingMode attached property to "NearestNeighbor". Note that even this will be ignored if the tree is rotated, and bilinear sampling will be used instead. It remains true that on certain GPUs, effects like DropShadow can encounter aliasing artifacts when using NearestNeighbor sampling.

Also note that when applying an effect to an image, the same RenderOptions.BitmapScalingMode attached property value will be used for both the image contents and the effect intermediate texture. If you want an image with NearestNeighbor sampling, but the effect to use Linear sampling, then you will need to apply the effect to a container of the image, such as a simple canvas containing the image.

like image 43
Dwayne Need Avatar answered Oct 04 '22 15:10

Dwayne Need