dotnet / wpf

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

Very low Text Rendering Performance due native C++ interop #4768

Open deeprobin opened 3 years ago

deeprobin commented 3 years ago

Would it not make sense to write the measure method in native C++ somehow to optimize the performance accordingly or is the overhead here too large?

Minimal repro:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <!-- ... Many column definitions -->
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Heigth="*"/>
        <!-- ... Many row definitions -->
    </Grid.RowDefinitions>
    <Button Grid.Row="0" Grid.Column="0">Test</Button>
    <Button Grid.Row="1" Grid.Column="0">Test</Button>
    <Button Grid.Row="0" Grid.Column="2">Test</Button>
    <Button Grid.Row="2" Grid.Column="3">Test</Button>
    <Button Grid.Row="12" Grid.Column="0">Test</Button>
    <Button Grid.Row="7" Grid.Column="8">Test</Button>
    <!-- ... big visual tree -->
</Grid>
deeprobin commented 3 years ago

image This are the hot spots of UIElement.Measure. Would it perhaps be useful to take a closer look at the TextAnalyzer performance-wise?

There is the fastest font renderer in the Rust language. Would make sense to create bindings for it and let fontdue do the work. Or what do you think?

Symbai commented 3 years ago

Instead of switching from C++ to Rust and still having calls to native code (with allocations and overhead), wouldn't it be better to check if the code can be replaced with C# instead? Now that we have Span etc. I don't know if we need any native code here at all. In the .NET runtime repo they're trying to get rid of all native calls because of performance issues.

deeprobin commented 3 years ago

@Symbai You're right about that. Are there any benchmarks on how much performance the native overhead takes away? And how does it look with the Ahead-of-time compilation, there should be no overhead in my opinion. Would it make sense to distinguish between AOT and JIT compilation (with e.g. preprocessor expressions)? What should also be noted, in my opinion, is that performance has already been radically improved in interoperability (See this article).

Symbai commented 3 years ago

WPF does not support AOT. There were benchmarks on .NET repo on each commit where they got rid of native code but I don't have any links. The best would be someone (else, because I have no idea what the code does) re-writes the code in C# and compare both. But then it needs to be reviewed by the new WPF repo owners and they are not very good on that.

miloush commented 3 years ago

@deeprobin can you provide a repro sample?

IAmTheCShark commented 3 years ago

@miloush I think this should be easily achieveable when you create a large grid view with lots of columns and rows and then scroll thorugh it. When virtualization is enabled, textblocks get lots of updates.

TextblockPerformance.zip

deeprobin commented 3 years ago

@IAmTheCShark This is exactly what I meant in DataGrid and this makes scrolling very unperformant. And it shouldn't be like that. Thank you for providing the example :).

IAmTheCShark commented 3 years ago

@deeprobin I wrote a small hack that uses reflection to turn on some optimizations. Just out of curiosity, can you give it a try and see if there are any improvements?

Obviously, this is nothing for productoin enivronment and only for testing purposes. This might have weird consequences so use with cuaiton.

The hack will make Typeface.CheckFastPathNominalGlyphs (https://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/Media/Typeface.cs,550) return true alot more often

Paste the following code into your MainWindow constructor before InitializeComponent or before you create all the TextBlocks. The first line creates a Typeface. If you have different FontFamilies, Styles and so on, you might need to call this multiple times. `

        Typeface typeFace = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);

        var tryGetGlyphTypeFaceMethod =
            typeof(Typeface).GetMethod("TryGetGlyphTypeface", BindingFlags.Instance | BindingFlags.NonPublic);

        GlyphTypeface glyphTypeFace = tryGetGlyphTypeFaceMethod.Invoke(typeFace, null) as GlyphTypeface;

        var fontFaceLayoutInfoProperty =
            typeof(GlyphTypeface).GetProperty("FontFaceLayoutInfo", BindingFlags.Instance | BindingFlags.NonPublic);

        var fontFaceLayoutInfo = fontFaceLayoutInfoProperty.GetValue(glyphTypeFace);
        var fontFaceLayoutInfoType = fontFaceLayoutInfo.GetType();
        var typographyAvailabilitiesField = fontFaceLayoutInfoType
            .GetField("_typographyAvailabilities", BindingFlags.NonPublic | BindingFlags.Instance);

        var typographyAvailabilitiesProperty
            = fontFaceLayoutInfoType
                .GetProperty("TypographyAvailabilities", BindingFlags.Instance | BindingFlags.NonPublic);

        // forces initialization of a few things
        typographyAvailabilitiesProperty.GetValue(fontFaceLayoutInfo);

        // the actual hack, fake some enum value that is suitable
        typographyAvailabilitiesField.SetValue(fontFaceLayoutInfo, 16);

`

deeprobin commented 3 years ago

Hey @IAmTheCShark, I just tried your Reflection hack. It makes a minimal difference for me, but it's not an improvement that drastically improves performance.

Has the reimplementation in C# already been started, if so, does a branch already exist for it?

deeprobin commented 2 years ago

@miloush I think this should be easily achieveable when you create a large grid view with lots of columns and rows and then scroll thorugh it. When virtualization is enabled, textblocks get lots of updates.

TextblockPerformance.zip

I have adapted your solution a bit and added a custom rendering using SkiaSharp and notice that the scrolling is significantly smoother.

Maybe it would be the best solution in the long run to switch from DWrite to SkiaSharp (first for the text rendering). TextblockPerformance.zip - with Skia