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

API Proposal: More performant overloads for Graphics.GetContextInfo #47880

Closed
JeremyKuhne opened this issue Feb 4, 2021 · 11 comments
Closed
Labels
api-approved API was approved in API review, it can be implemented area-System.Drawing tenet-performance Performance related issue
Milestone

Comments

@JeremyKuhne
Copy link
Member

JeremyKuhne commented Feb 4, 2021

Background and Motivation

Graphics.GetContextInfo exists to allow WinForms to apply cumulative Graphics transform translation and clipping to the underlying HDC so that rendering via GDI has similar output. This method currently returns an object array of [ Region, Matrix ]. (Note that the Matrix is the current Transform with only the cumulative offset applied.)

This API is called many times during rendering of WinForms applications. Avoiding the array allocation would make a significant difference in rendering allocations. A much greater allocation reduction would be obtained if Graphics tracked if the .Clip and .Transform setters are ever accessed to allow an early out in the method logic.

WinForms also don't always need both values. System.Drawing can't calculate the clipping region without the offset, but it can calculate the offset without the Region. Making the Region optional will save significant overhead.

In .NET 5.0 we added a lot of logic to WinForms to try and track and avoid getting this data. Tracking the "dirty" state in System.Drawing covers far more scenarios and allows significantly simpler code in WinForms.

Proposed API

namespace System.Drawing
{
    public sealed class Graphics
    {
         public object GetContextInfo();
+        public void GetContextInfo(out PointF offset);
+        public void GetContextInfo(out PointF offset, out Region clip);
    }
}
  • out Region clip would return null when Clip and Transform have never been set.
  • out Region clip would return null if the cumulative region ends up .IsInfinite() (meaningless)
  • out Point offset will return an empty (default) PointF when Transform has never been set.
  • existing GetContextInfo behavior won't change for compat

Usage Examples

The existing method is used in WinForms here.

Risks

If we missed a case where early out is inappropriate we'd have to adjust for that, but that is an internal detail.

More Details

  • GDI+ "layers" its clip and transform on top of the underlying GDI HDC. So a newly created Graphics object always has an infinite clip and identity matrix.
  • Restriction to translation in the Matrix is probably due to lack of Win9x support for rotation (not positive).

cc: @RussKie @merriemcgaw

@JeremyKuhne JeremyKuhne added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Drawing tenet-performance Performance related issue labels Feb 4, 2021
@JeremyKuhne JeremyKuhne added this to the 6.0.0 milestone Feb 4, 2021
@ghost
Copy link

ghost commented Feb 4, 2021

Tagging subscribers to this area: @safern, @tannergooding
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and Motivation

Graphics.GetContextInfo exists to allow WinForms to apply cumulative Graphics transform translation and clipping to the underlying HDC so that rendering via GDI has similar output. This method currently returns an object array of [ Region, Matrix ].

This API is called many times during rendering of WinForms applications. Avoiding the array allocation would make a significant difference in rendering allocations. A much greater allocation reduction would be obtained if Graphics tracked if the .Clip and .Transform setters are ever accessed to allow an early out in the method logic.

WinForms also don't always need both values. System.Drawing can't calculate the clipping region without the offset, but it can calculate the offset without the Region. Making the Region optional will save significant overhead.

In .NET 5.0 we added a lot of logic to WinForms to try and track and avoid getting this data. Tracking the "dirty" state in System.Drawing covers far more scenarios and allows significantly simpler code in WinForms.

Proposed API

namespace System.Drawing
{
    public sealed class Graphics
    {
         public object GetContextInfo();
+        public void GetContextInfo(out Point offset);
+        public void GetContextInfo(out Point offset, out Region clip);
    }
}
  • out Region clip would return null when Clip and Transform have never been set.
  • out Region clip would return null if the cumulative region ends up .IsInfinite() (meaningless)
  • out Point offset will return an empty (default) Point when Transform has never been set.
  • existing GetContextInfo behavior won't change for compat

Usage Examples

The existing method is used in WinForms here.

Risks

No known risks.

cc: @RussKie @merriemcgaw

Author: JeremyKuhne
Assignees: -
Labels:

api-suggestion, area-System.Drawing, tenet-performance

Milestone: 6.0.0

@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Feb 4, 2021
@JeremyKuhne JeremyKuhne changed the title API Proposal: More performant overload for Graphics.GetContextInfo API Proposal: More performant overloads for Graphics.GetContextInfo Feb 4, 2021
@safern safern added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation untriaged New issue has not been triaged by the area owner labels Feb 5, 2021
@safern
Copy link
Member

safern commented Feb 5, 2021

We've previously discussed this offline and makes sense, the current GetContextInfo is pretty expensive.

@tannergooding
Copy link
Member

Is returning via out preferred to returning a value tuple here?

@safern
Copy link
Member

safern commented Feb 5, 2021

Is returning via out preferred to returning a value tuple here?

You mean an out value tuple? We wouldn't be able to add a parameter-less overload that returns a value tuple as that conflicts with the existing override.

@tannergooding
Copy link
Member

You mean an out value tuple? We wouldn't be able to add a parameter-less overload that returns a value tuple as that conflicts with the existing override.

Right, although I imagine we could also something like:

  • have a different overload
  • have something like T GetContextInfo<T>() where T : struct and then specialize based on what info we need
  • have explicit Point GetContextOffset and Region GetContextClip

I think the most interesting thing here is that GetContextInfo returned object so that we could have public surface area while also having that info be "opaque".
Having these new methods means that the context info is no longer opaque and so it might be worth exposing that info slightly differently or via a proper struct if we expect it might change in the future.

@safern
Copy link
Member

safern commented Feb 5, 2021

I think I like that idea better and might be more extensible.

@JeremyKuhne thoughts?

@JeremyKuhne
Copy link
Member Author

I updated the proposal from Point to PointF as that is what is currently returned (whoops).

The Region depends on calculating the offset so having separate methods would defeat the performance need here when we need both (which is often).

I'm unclear about the value of being open-ended as GraphicsContext only has these two properties (PointF and Region). I suspect the likelihood of ever adding anything else to this is pretty small. We might want to expose the internal "changed" flags to allow for optimizing getting only the current Clip and Transform by having a flag here? That said, a quick look through the WinForms runtime I don't see where we'd leverage that currently.

@JeremyKuhne
Copy link
Member Author

Tagging #47940 as it is somewhat related.

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Feb 9, 2021
@terrajobst
Copy link
Member

terrajobst commented Feb 9, 2021

Video

  • The existing GetContextInfo() is hidden from IntelliSense. We should mark it as [Obsolete] and point people to the new APIs, at the off chance that someone is using the old one.
  • Otherwise, looks good.
namespace System.Drawing
{
    public sealed class Graphics
    {
        [Obsolete("Use one of the other overloads.")]
        public object GetContextInfo();
        public void GetContextInfo(out PointF offset);
        public void GetContextInfo(out PointF offset, out Region clip);
    }
}

JeremyKuhne added a commit to JeremyKuhne/runtime that referenced this issue Apr 15, 2021
Implements dotnet#47880, adding new, more performant overloads for GetContextInfo.

- Add helper for only creating a region if it isn't infinite
- Start an internal extensions class for easier mapping of System.Drawing concepts to System.Numerics types
- Simplify GraphicsContext
safern added a commit that referenced this issue Apr 16, 2021
* Add basic GetContextInfo regression tests.

* Implement new GetContextInfo API overloads

Implements #47880, adding new, more performant overloads for GetContextInfo.

- Add helper for only creating a region if it isn't infinite
- Start an internal extensions class for easier mapping of System.Drawing concepts to System.Numerics types
- Simplify GraphicsContext

* Update WindowsGraphics to use new overloads.

* Skip tests when GDI+ isn't available.

* Add `[SupportedOSPlatform("windows")]`

* Use new Obsolete attribute pattern.

* Use Obsoletions.cs and conditionalize in ref.

* Address feedback

* Conditionalize the obsoletion in the src

* Update suppression in tests

* Apply obsoletions downlevel using an internal ObsoleteAttribute

Co-authored-by: Jeff Handley <jeff.handley@microsoft.com>
Co-authored-by: Santiago Fernandez Madero <safern@microsoft.com>
@stephentoub
Copy link
Member

Should this issue be closed now?

@safern
Copy link
Member

safern commented Apr 22, 2021

Yeah. Thanks, @stephentoub

@safern safern closed this as completed Apr 22, 2021
@ghost ghost locked as resolved and limited conversation to collaborators May 22, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Drawing tenet-performance Performance related issue
Projects
None yet
Development

No branches or pull requests

5 participants