Skip to content

Commit

Permalink
Remove WindowsGraphics and DeviceContext (#3553)
Browse files Browse the repository at this point in the history
This change removes all of the misc\GDI classes and replaces them with
new isolated caches for Font-to-HFONT and "measurement" screen DCs.

This reduces significant allocations and complexity and improves performance
with these legacy code paths.

Fundamental components:

-Fully eliminates all existing finalizable GDI wrappers and caches (from GDI\misc)
-Replaces DeviceContext and WindowsGraphics with DeviceContextHdcScope
-Font to HFONT caching is contained in FontCache
-Screen HDC's are now managed by ScreenDcCache
-GdiCache is the entry point for GDI object caches
-Starts implementing IDeviceContext on event args as a method for smuggling through existing IDeviceContext APIs to DeviceContextHdcScope, which looks for
the internal IGraphicsHdcProvider that the event args also implement
-HDC/Graphics optimization is wrapped in DrawingEventArgs struct that events can contain to manage consistently
-Hacky extension TryGetGraphics() allows us to refactor APIs to use IDeviceContext when there is still a dependency on Graphics for some code paths (e.g. transparent color drawing)
-Add a few new drawing helpers in DeviceContextExtensions
-Some initial fiddling with GDI+ direct usage- will iterate on where that lands in upcoming PRs
-Some APIs no longer throw ArgumentNullException, updated a number of tests
-Various style cleanups as I was going through investigating call chains
-Eliminate unnecessary state saving or Graphics wrapping on a number of call sites
  • Loading branch information
JeremyKuhne committed Jul 18, 2020
1 parent dec24b8 commit a6a308f
Show file tree
Hide file tree
Showing 121 changed files with 5,567 additions and 6,084 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ protected override void OnPaint(PaintEventArgs pevent)

ButtonRenderer.DrawButton(g, bounds, State);

Color color = SystemColors.ButtonHighlight;
_dropDownRectangle = new Rectangle(bounds.Right - PushButtonWidth - 1, 4, PushButtonWidth, bounds.Height - 8);

if (RightToLeft == RightToLeft.Yes)
Expand Down Expand Up @@ -656,7 +657,7 @@ protected override void OnPaint(PaintEventArgs pevent)

if (!string.IsNullOrEmpty(Text))
{
TextRenderer.DrawText(g, Text, Font, bounds, SystemColors.ControlText, formatFlags);
TextRenderer.DrawText(pevent, Text, Font, bounds, SystemColors.ControlText, formatFlags);
}

if (Focused)
Expand All @@ -665,9 +666,11 @@ protected override void OnPaint(PaintEventArgs pevent)
}
}

private void PaintArrow(Graphics g, Rectangle dropDownRect)
private void PaintArrow(IDeviceContext deviceContext, Rectangle dropDownRect)
{
Point middle = new Point(Convert.ToInt32(dropDownRect.Left + dropDownRect.Width / 2), Convert.ToInt32(dropDownRect.Top + dropDownRect.Height / 2));
Point middle = new Point(
Convert.ToInt32(dropDownRect.Left + dropDownRect.Width / 2),
Convert.ToInt32(dropDownRect.Top + dropDownRect.Height / 2));

// If the width is odd - favor pushing it over one pixel right.
middle.X += (dropDownRect.Width % 2);
Expand All @@ -678,7 +681,7 @@ private void PaintArrow(Graphics g, Rectangle dropDownRect)
new Point(middle.X, middle.Y + s_offset2Y)
};

g.FillPolygon(SystemBrushes.ControlText, arrow);
deviceContext.TryGetGraphics(create: true).FillPolygon(SystemBrushes.ControlText, arrow);
}

private void ShowContextMenuStrip()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal static partial class Interop
{
internal static partial class Gdi32
{
[DllImport(Libraries.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
[DllImport(Libraries.Gdi32, SetLastError = true, ExactSpelling = true)]
public static extern BOOL BitBlt(
HDC hdc,
int x,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ internal static partial class Gdi32
/// </summary>
public CreateBrushScope(Color color)
{
HBrush = CreateSolidBrush(ColorTranslator.ToWin32(color));
}
HBrush = color.IsSystemColor
? User32.GetSysColorBrush(ColorTranslator.ToOle(color) & 0xFF)
: CreateSolidBrush(ColorTranslator.ToWin32(color));
}

public static implicit operator HBRUSH(in CreateBrushScope scope) => scope.HBrush;
public static implicit operator HGDIOBJ(in CreateBrushScope scope) => scope.HBrush;
Expand All @@ -37,6 +39,7 @@ public void Dispose()
{
if (!HBrush.IsNull)
{
// Note that this is a no-op if the original brush was a system brush
DeleteObject(HBrush);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,5 @@ internal static partial class Gdi32
{
[DllImport(Libraries.Gdi32, ExactSpelling = true)]
public static extern HDC CreateCompatibleDC(HDC hDC);

public static HDC CreateCompatibleDC(HandleRef hDC)
{
HDC result = CreateCompatibleDC((HDC)hDC.Handle);
GC.KeepAlive(hDC.Wrapper);
return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public CreatePenScope(Color color, int width = 1)
}

public static implicit operator HPEN(in CreatePenScope scope) => scope.HPen;
public static implicit operator HGDIOBJ(in CreatePenScope scope) => scope.HPen;

public bool IsNull => HPen.IsNull;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Gdi32
{
[DllImport(Libraries.Gdi32, ExactSpelling = true)]
public static extern RegionType GetClipBox(HDC hdc, ref RECT lprect);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,8 @@ public enum DeviceCapability : int
LOGPIXELSY = 90
}

[SuppressGCTransition]
[DllImport(Libraries.Gdi32, SetLastError = true, ExactSpelling = true)]
public static extern int GetDeviceCaps(HDC hDC, DeviceCapability nIndex);

public static int GetDeviceCaps(IHandle hDC, DeviceCapability nIndex)
{
int caps = GetDeviceCaps((HDC)hDC.Handle, nIndex);
GC.KeepAlive(hDC);
return caps;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,26 @@ public SelectPaletteScope(HDC hdc, HPALETTE hpalette, bool forceBackground, bool
}

public static SelectPaletteScope HalftonePalette(HDC hdc, bool forceBackground, bool realizePalette)
=> new SelectPaletteScope(hdc, (HPALETTE)Graphics.GetHalftonePalette(), forceBackground, realizePalette);
{
if (GetDeviceCaps(hdc, DeviceCapability.BITSPIXEL) > 8)
{
// https://docs.microsoft.com/windows/win32/api/Gdiplusgraphics/nf-gdiplusgraphics-graphics-gethalftonepalette
// The purpose of the Graphics::GetHalftonePalette method is to enable GDI+ to produce a better
// quality halftone when the display uses 8 bits per pixel. This method allocates a palette of
// 256 entries (each of which are 4 bytes a piece).
//
// Doing this is a bit pointless when the color depth is much higher (the normal scenario). As such
// we'll skip doing this unless we see 8bpp or less.

return default;
}

return new SelectPaletteScope(
hdc,
(HPALETTE)Graphics.GetHalftonePalette(),
forceBackground,
realizePalette);
}

public static implicit operator HPALETTE(in SelectPaletteScope paletteScope) => paletteScope.HPalette;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,30 @@ internal static partial class Gdi32
public HGDIOBJ PreviousObject { get; }

/// <summary>
/// Selects <paramref name="object"/> into the given <paramref name="hdc"/>.
/// Selects <paramref name="object"/> into the given <paramref name="hdc"/> using
/// <see cref="SelectObject(HDC, HGDIOBJ)"/>.
/// </summary>
public SelectObjectScope(HDC hdc, HGDIOBJ @object)
{
_hdc = hdc;
PreviousObject = SelectObject(hdc, @object);
// Selecting null doesn't mean anything

if (@object.IsNull)
{
this = default;
}
else
{
_hdc = hdc;
PreviousObject = SelectObject(hdc, @object);
}
}

public void Dispose()
{
SelectObject(_hdc, PreviousObject);
if (!_hdc.IsNull)
{
SelectObject(_hdc, PreviousObject);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Drawing;

internal static partial class Interop
{
internal static partial class Gdi32
{
/// <summary>
/// Helper to scope selecting a given background color into a HDC. Restores the original background color into
/// the HDC when disposed.
/// </summary>
/// <remarks>
/// Use in a <see langword="using" /> statement. If you must pass this around, always pass by
/// <see langword="ref" /> to avoid duplicating the handle and resetting multiple times.
/// </remarks>
internal readonly ref struct SetBackgroundColorScope
{
private readonly int _previousColor;
private readonly HDC _hdc;

/// <summary>
/// Sets text color <paramref name="color"/> in the given <paramref name="hdc"/> using
/// <see cref="SetBkColor(HDC, int)"/>.
/// </summary>
public SetBackgroundColorScope(HDC hdc, Color color)
{
int colorref = ColorTranslator.ToWin32(color);
_previousColor = SetBkColor(hdc, colorref);

// If we didn't actually change the color, don't keep the HDC so we skip putting back the same state.
_hdc = colorref == _previousColor ? default : hdc;
}

public void Dispose()
{
if (!_hdc.IsNull)
{
SetBkColor(_hdc, _previousColor);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,8 @@ internal static partial class Interop
{
internal static partial class Gdi32
{
[SuppressGCTransition]
[DllImport(Libraries.Gdi32, ExactSpelling = true)]
public static extern BKMODE SetBkMode(HDC hdc, BKMODE mode);

public static BKMODE SetBkMode(IHandle hdc, BKMODE mode)
{
BKMODE result = SetBkMode((HDC)hdc.Handle, mode);
GC.KeepAlive(hdc);
return result;
}

public static BKMODE SetBkMode(HandleRef hdc, BKMODE mode)
{
BKMODE result = SetBkMode((HDC)hdc.Handle, mode);
GC.KeepAlive(hdc.Wrapper);
return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Drawing;

internal static partial class Interop
{
internal static partial class Gdi32
{
/// <summary>
/// Helper to scope selecting a given mapping mode into a HDC. Restores the original mapping mode into the HDC
/// when disposed.
/// </summary>
/// <remarks>
/// Use in a <see langword="using" /> statement. If you must pass this around, always pass by
/// <see langword="ref" /> to avoid duplicating the handle and resetting multiple times.
/// </remarks>
internal readonly ref struct SetMapModeScope
{
private readonly MM _previousMapMode;
private readonly HDC _hdc;

/// <summary>
/// Sets the <paramref name="mapMode"/> in the given <paramref name="hdc"/> using
/// <see cref="SetMapMode(HDC, MM)"/>.
/// </summary>
public SetMapModeScope(HDC hdc, MM mapMode)
{
_previousMapMode = SetMapMode(hdc, mapMode);

// If we didn't actually change the map mode, don't keep the HDC so we skip putting back the same state.
_hdc = mapMode == _previousMapMode ? default : hdc;
}

public void Dispose()
{
if (!_hdc.IsNull)
{
SetMapMode(_hdc, _previousMapMode);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ internal static partial class Interop
internal static partial class Gdi32
{
/// <summary>
/// Helper to scope selecting a given foreground mix mode into a HDC. Restores the original
/// mix mode into the HDC when disposed.
/// Helper to scope selecting a given foreground mix mode into a HDC. Restores the original mix mode into the
/// HDC when disposed.
/// </summary>
/// <remarks>
/// Use in a <see langword="using" /> statement. If you must pass this around, always pass
/// by <see langword="ref" /> to avoid duplicating the handle and resetting multiple times.
/// Use in a <see langword="using" /> statement. If you must pass this around, always pass by
/// <see langword="ref" /> to avoid duplicating the handle and resetting multiple times.
/// </remarks>
internal readonly ref struct SetRop2Scope
{
private readonly R2 _previousRop;
private readonly HDC _hdc;

/// <summary>
/// Selects <paramref name="rop2"/> into the given <paramref name="hdc"/>.
/// Selects <paramref name="rop2"/> into the given <paramref name="hdc"/> using <see cref="SetROP2(HDC, R2)"/>.
/// </summary>
public SetRop2Scope(HDC hdc, R2 rop2)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ internal static partial class Interop
internal static partial class Gdi32
{
[DllImport(Libraries.Gdi32, ExactSpelling = true)]
public static extern TA SetTextAlign(IntPtr hdc, TA align);
public static extern TA SetTextAlign(Gdi32.HDC hdc, TA align);

public static TA SetTextAlign(IHandle hdc, TA align)
{
TA result = SetTextAlign(hdc.Handle, align);
TA result = SetTextAlign((Gdi32.HDC)hdc.Handle, align);
GC.KeepAlive(hdc);
return result;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;

internal static partial class Interop
{
internal static partial class Gdi32
{
/// <summary>
/// Helper to scope selecting a given text alignment mode into a HDC. Restores the original text alignment
/// mode into the HDC when disposed.
/// </summary>
/// <remarks>
/// Use in a <see langword="using" /> statement. If you must pass this around, always pass by
/// <see langword="ref" /> to avoid duplicating the handle and resetting multiple times.
/// </remarks>
internal readonly ref struct SetTextAlignmentScope
{
private readonly TA _previousTa;
private readonly HDC _hdc;

/// <summary>
/// Sets <paramref name="ta"/> in the given <paramref name="hdc"/> using <see cref="SetTextAlign(HDC, TA)"/>.
/// </summary>
public SetTextAlignmentScope(HDC hdc, TA ta)
{
_previousTa = SetTextAlign(hdc, ta);

// If we didn't actually change the TA, don't keep the HDC so we skip putting back the same state.
_hdc = _previousTa == ta ? default : hdc;
}

public void Dispose()
{
if (!_hdc.IsNull)
{
SetTextAlign(_hdc, _previousTa);
}
}
}
}
}

0 comments on commit a6a308f

Please sign in to comment.