| File: System\Windows\Forms\Rendering\ScreenDcCache.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Numerics; namespace System.Windows.Forms; /// <summary> /// Collection of screen device contexts. /// </summary> /// <remarks> /// <para> /// This caching counts on consumers not leaving the HDC in a dirty state. There is a significant overhead to /// saving and restoring the state which would make this cache much less impactful. If we can't have confidence /// the DC state is being restored the best option may be to simply create a brand new screen DC every time we /// need one. /// </para> /// <para> /// Creating a screen DC from scratch and deleting it takes about 11us. Saving and restoring takes about half /// that time. Renting an existing DC from the cache and returning it is on the order of 20_ns_. If we were /// forced to save state we'd be taking about 9us renting and returning cached DCs- which would take around /// 20 rentals to break even with simply creating and tossing away a brand new DC. /// </para> /// </remarks> internal sealed partial class ScreenDcCache : IDisposable { private readonly IntPtr[] _itemsCache; /// <summary> /// Create a cache with space for the specified number of HDCs. /// </summary> public ScreenDcCache(int cacheSpace = 5) { Debug.Assert(cacheSpace >= 0); _itemsCache = new IntPtr[cacheSpace]; } /// <summary> /// Get a DC from the cache or create one if none are available. /// </summary> public ScreenDcScope Acquire() { IntPtr item; for (int i = 0; i < _itemsCache.Length; i++) { item = Interlocked.Exchange(ref _itemsCache[i], IntPtr.Zero); if (item != IntPtr.Zero) { return new ScreenDcScope(this, (HDC)item); } } // Didn't find anything in the cache, create a new HDC return new ScreenDcScope(this, PInvokeCore.CreateCompatibleDC(default)); } /// <summary> /// Release an item back to the cache, disposing if no room is available. /// </summary> private void Release(HDC hdc) { ArgumentValidation.ThrowIfNull(hdc); ValidateHdc(hdc); IntPtr temp = (IntPtr)hdc; for (int i = 0; i < _itemsCache.Length; i++) { // Flip with the array until we get back an empty slot temp = Interlocked.Exchange(ref _itemsCache[i], temp); if (temp == IntPtr.Zero) { return; } } // Too many to store, delete the last item we swapped. PInvokeCore.DeleteDC((HDC)temp); } ~ScreenDcCache() => Dispose(); public void Dispose() { for (int i = 0; i < _itemsCache.Length; i++) { IntPtr hdc = _itemsCache[i]; if (hdc != IntPtr.Zero) { PInvokeCore.DeleteDC((HDC)hdc); } } } [Conditional("DEBUG")] private static unsafe void ValidateHdc(HDC hdc) { // A few sanity checks against the HDC to see if it was left in a dirty state HRGN hrgn = PInvoke.CreateRectRgn(0, 0, 0, 0); Debug.Assert(PInvoke.GetClipRgn(hdc, hrgn) == 0, "Should not have a clipping region"); PInvokeCore.DeleteObject(hrgn); Point point; PInvokeCore.GetViewportOrgEx(hdc, &point); Debug.Assert(point.IsEmpty, "Viewport origin shouldn't be shifted"); Debug.Assert(PInvoke.GetMapMode(hdc) == HDC_MAP_MODE.MM_TEXT); Debug.Assert(PInvoke.GetROP2(hdc) == R2_MODE.R2_COPYPEN); Debug.Assert(PInvoke.GetBkMode(hdc) == BACKGROUND_MODE.OPAQUE); Matrix3x2 matrix = default; Debug.Assert(PInvokeCore.GetWorldTransform(hdc, (XFORM*)(void*)&matrix)); Debug.Assert(matrix.IsIdentity); } }