-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[Performance] Rewrite DirectWriteForwarder in C# to improve rendering performance #5305
Comments
Hi, I did this last year but I needed a small C component to get around some COM interop issues. https://github.com/madewokherd/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Factory.cs |
@madewokherd Oh cool 👍🏼 Is in this already in Pull Request? |
No, I did it because I needed a way to build it on Linux using open source tools for wine-mono. I didn't expect it to be useful for upstream. It probably needs to be cleaned up. The few C functions needed could be moved to an existing dll, or maybe ComWrappers would remove the need for them. There's still TrueTypeSubsetter which I didn't touch at all, it doesn't seem to be commonly used and could be moved to pure C++ I think. And the C# code probably shouldn't be mixed in with existing C++ code. It also needs build system updates as I just used my own build system (my requirements were to build on Linux with Framework Mono, so the existing build files were not usable for my purposes). I'm skeptical that this will improve performance. We still have to call into dwrite.dll so the native interop is just done in a different place. Maybe I can at least put something together so you can do performance testing, once I find some free time. |
Hey, this is a great idea and I've actually been working in my free time on converting DirectWriteForwarder to C# for the past couple of weeks. The performance from the move from C++/CLI shouldn't be that noticeable since, as @madewokherd stated, we're still calling into dwrite.dll. But, there are other advantages, here are some in no particular order:
For now, the prototype that I'm working on is not open source because it's not working yet, but I'm making progress. I took a slightly different approach than @madewokherd, I use structs for DWrite objects that I wrap in a CriticalHandle so the lifetime management is done by the GC and we almost never have to manually release them. We don't really need ComWrapper because if I recall correctly, DWrite is not "true COM". Most of DWrite objects are created by there parent object and one-way only (DWrite -> Managed). Keep in mind that what I said might be totally untrue but for now, what I've done seems to be working. We would also need confirmation from someone in the WPF team that they would be interested in a community PR for this, because if it's not built-in, the usefulness would be really limited. Thanks! |
Apparently the part I had trouble with was implementing the IDWriteFontCollectionLoader and IDWriteFontFileLoader interfaces on the C# side. We would sometimes get calls to these interfaces after calling the corresponding IDWriteFactory::Unregister* method. I don't remember exactly why that was a problem, but I was convinced at the time that I couldn't fix it while using builtin COM support to implement the interface, and I didn't want to rewrite it based on delegates. Incidentally, that's the sort of reference cycle involving unmanaged objects that I don't think the GC will be able to detect for you. |
I didn't hit this yet so maybe my app doesn't go far enough or it's something specific to built-in COM. I'll look into it once I hit this but thanks for the info! My current plan for the port is this:
I'm currently doing steps 4 to 6. This might not be the most efficient way but I wanted to do it small steps at a time to make sure that every method works and that it doesn't start breaking everywhere. This is a direct port so there are no changes other than porting C++/CLI code to C#. |
@ThomasGoulet73 |
I have created a benchmark for DWrite in general. This benchmark covers the direct call of the methods via COM. Benchmark on @ThomasGoulet73 But of course the first step is to get rid of the C++/CLI code. |
I have some updates, I ported a good chunk of C++/CLI code to C#, enough to run a simple app without needing any C++/CLI code. I'll start cleaning my port and I will push it to my fork when it's in a "good enough" state. There's still a large task of testing everything but from my initial testing, it seems to work. One nice thing that I noticed is that I was now able to trim a simple WPF app with few or no linker hints/configurations. |
@ThomasGoulet73 Oh thats nice. Thanks for the time you put into this. |
I pushed my changes to a branch in my fork https://github.com/ThomasGoulet73/wpf/tree/managed-dwrite for those of you who want to try it. There are few methods that are still not implemented because I didn't hit them yet. My next step is to do some testing using WPF samples and then try to split this branch in multiple PRs to try and merge it progressively. |
Oh that seems great. Good work! A small thing, besides the unimplemented methods: Couldn't this theoretically be converted to a |
@deeprobin That's some nice suggestions but for now I'm not really looking at performance optimizations. Span could probably be generic because according to this comment, it was created before C# supported generics. I'll try to take a deeper look at your suggestions once I'm done with the full port of DirectWriteForwarder to managed. |
@ThomasGoulet73 Great. Thanks a lot :) |
@ThomasGoulet73 this looks like a nice effort - awesome! Instead of defining your own P/Invoke and struct definitions, see if you can make use of CsWin32. I was recently using it for something and was able to leverage many IDWritexx COM interfaces defined by CSWin32 automatically. I was thinking that would really make DirectWriteForwarder C#-ification easier and came here to check what's going on - turns out you're already working on it 👍 |
@vatsan-madhavan That's great, I didn't know that CsWin32 was able to generate DirectX csharp definitions. I'll try to have a look on how to integrate this on my branch though I'm hesitant to use it because it's still in beta. Having handwritten DirectWrite csharp definitions (Like on my branch) is a bit problematic because there are no guarantee that they are actually right, it would only fail at runtime. |
If you're doing interop to DWrite in C#, I highly recommend making use of @tannergooding 's TerraFX.Interop.Windows package. It's written to be as low-overhead as possible, and will be the best performing solution (I did take a look at e.g. https://github.com/ThomasGoulet73/wpf/tree/managed-dwrite and it is using some patterns, such as liberal use of |
Also noting that TerraFX.Interop.Windows is just using |
Thanks @rickbrew and @tannergooding for the suggestion! I don't think we could use TerraFX.Interop.Windows in WPF as a dependency because of its size. It might be highly trimmable but WPF is not trimmable. So it looks like there's 3 choices (In no particular order):
These 3 choices do not change the code in WPF since they all produce similar code. For now, I'll keep writing it by hand but the other choices should definitely be considered. I'll let the WPF team make the final decision if/when the time comes of merging my branch upstream. |
Using TerraFX.Interop.Windows in WPF probably isn't feasible (I had misunderstood and thought we were discussing a separate standalone thing); but pulling parts of it in from source or utilizing |
Yeah, I meant to say to copy the code from TerraFX, not reference the package itself. |
@tannergooding I think I'll do what you suggested and use |
I don't think it matters what library to choose. |
So seems to be that’s unlock me on WPF and NativeAOT support. @ThomasGoulet73 you should expect contributions when this appear on my radar |
Okay I take a look at the source code only. Did not have access to Windows PC right now (will test in next couple days max). I would like to discuss following idea:
First 3 bullet points is attempt to account for situation is MS will crawl with adding support for these changes for any reason. That hedge risks without knowing what direction WPF team takes. What do all of you thinking? Is this practical suggestions/ideas? |
@ThomasGoulet73 when trying to build from aforementioned branch, I receive bunch of C++ errors like this
Is this expected? |
Contributes to dotnet#5305
Contributes to dotnet#5305
With my changes in https://github.com/ThomasGoulet73/wpf/commits/managed-dwrite + some of @kant2002's work on ComWrappers, I am now able to compile a simple WPF app with NativeAOT. Some things still require more ComWrappers work, like text input, drag drop, etc. I also had to disable some things in the WPF codebase + do some manual NativeAOT configuration in the project but atleast it runs. Trimming is also a big problem, you can't trim many WPF assemblies because of heavy use of reflection, like in XAML loading (It would require a XAML to C#/IL compiler). Right now, a simple app compiled with NativeAOT is ~85 mb. I'll try to push my NativeAOT experiment on a branch in my fork soon. |
How do you do that? I cannot compile until I almost fully rewrite DWriteForwarder (based on @ThomasGoulet73 work and continue it) and get rid of System.Printing and ReachFramework breaking compatibility for now. https://github.com/kant2002/wpf/tree/kant/no-com More additional work, and in general I’m tired waiting, so I plan first create at least some sort of working NativeAOT experience and then rethink how this should be rearchitected. What I learn so far: TTF parsing in native part not worth porting to C# and maybe provided using Pinvoke . That’s enough to move code in DirectWriteForwarer to C#. I order to did something with System.Printing probably first step is to break all cycles and make dependencies linear. Without that I cannot even migrate code step by step because I’m tired of chasing strange compilation errors. TextBox still not working yet. And more interfaces should be ported to ComWrappers. I welcome anybody to join. |
@kant2002 I just build the WPF codebase and publish with NativeAOT using those dlls. Then I just debug the app and wait for a crash. I couldn't get TextBox working because of the use of built-in Com interop which requires to be migrated to ComWrappers. For now, I tried Label, Button, TextBlock, DataGrid. My tests were not thorough but basic functionality works. When you say "I cannot compile until I almost fully rewrite DWriteForwarder" do you mean that the NativeAOT compilation fails or that the app from the NativeAOT compilation does not run ? |
Compiled app fails but in C++ module intialization and now I see the difference. I start with TextBox and did not test other controls. TextBox dig dee into MS Text Services Framework so that’s not unexpected. So probably if you want TextBox you are going to have more code adopted. I would try other controls, thanks for the tip! Printing would be hard. Probably we should collaborate more, you can drop me a line at email on my profile, if you like to chat |
Other important thing for visibility is to provide list of all built in controls and at least smoke check them . Similar to what I done with WinForms dotnet/runtimelab#1100 |
I got lucky because I tried Label and TextBlock first because I was working on DirectWriteForwarder and I only tried TextBox after I was able to compile using NativeAOT. I agree that Printing will probably be hard, I don't plan on working on it right now because I don't think that it is a priority.
This is a good idea because some controls will definitely be more complicated to support (Like TextBox). |
Next offender is wpf/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/SystemResources.cs Line 803 in 1442909
That's crashing on loading |
Only change which I do not place on the aforementioned branch is removing content of Lines 50 to 57 in 1442909
which does not work in NativeAOT for some reasons. I suspect that this is ILC bug. |
Contributes to dotnet#5305
Contributes to dotnet#5305
Can someone at Microsoft consider the outstanding job done by @kant2002 on here kant2002/WinFormsComInterop#30 ? EDIT: @kant2002 if I could, I would give you an Oscar |
Contributes to dotnet#5305
Contributes to dotnet#5305
Contributes to dotnet#5305
See #4768.
The current code is written in C++/CLI and I think that can be optimized. Unfortunately, I have written very little C++/CLI myself but I suspect that the generated IL code is smaller and better performing.
See also https://github.com/dotnet/wpf/tree/main/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder
The text was updated successfully, but these errors were encountered: