Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
deeprobin opened this issue Jun 29, 2021 · 10 comments
Open

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

deeprobin opened this issue Jun 29, 2021 · 10 comments
Labels
Investigate Requires further investigation by the WPF team. Performance Performance related issue
Milestone

Comments

@deeprobin
Copy link
Contributor

deeprobin commented Jun 29, 2021

  • .NET Core Version: 5.0.6
  • Windows version: 21H1 (Build 19043.928)
  • Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes
  • Is this bug related specifically to tooling in Visual Studio (e.g. XAML Designer, Code editing, etc...)?: No

Problem description:
UIElement.Measure this very slow

Actual behavior:
image

Expected behavior:
Fast measuring for WPF rendering.
If there is nothing more that can be optimized in the measure method, it might make sense to cache the measures according to certain parameters, since this is very slow with large visual trees.
Compared to Windows Forms, Windows Forms is superior in terms of performance here.

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
Copy link
Contributor Author

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
Copy link
Contributor

Symbai commented Jul 6, 2021

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
Copy link
Contributor Author

@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
Copy link
Contributor

Symbai commented Jul 7, 2021

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
Copy link
Contributor

miloush commented Jul 11, 2021

@deeprobin can you provide a repro sample?

@ryalanms ryalanms added Performance Performance related issue Investigate Requires further investigation by the WPF team. and removed Requires WPF team triage Untriaged labels Jul 12, 2021
@IAmTheCShark
Copy link

@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
Copy link
Contributor Author

deeprobin commented Jul 20, 2021

@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
Copy link

IAmTheCShark commented Aug 3, 2021

@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
Copy link
Contributor Author

deeprobin commented Aug 5, 2021

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 deeprobin changed the title UIElement.Measure is very slow. Very low Text Rendering Performance due native C++ interop Aug 19, 2021
@ryalanms ryalanms added this to the Future milestone Aug 27, 2021
@deeprobin
Copy link
Contributor Author

deeprobin commented Nov 30, 2021

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Investigate Requires further investigation by the WPF team. Performance Performance related issue
Projects
None yet
Development

No branches or pull requests

5 participants