diff --git a/src/samples/DirectDraw/Direct2dDemo/Direct2dDemo.csproj b/src/samples/DirectDraw/Direct2dDemo/Direct2dDemo.csproj
new file mode 100644
index 0000000..6888128
--- /dev/null
+++ b/src/samples/DirectDraw/Direct2dDemo/Direct2dDemo.csproj
@@ -0,0 +1,11 @@
+
+
+
+ WinExe
+
+
+
+
+
+
+
diff --git a/src/samples/DirectDraw/Direct2dDemo/Program.cs b/src/samples/DirectDraw/Direct2dDemo/Program.cs
new file mode 100644
index 0000000..c4808c7
--- /dev/null
+++ b/src/samples/DirectDraw/Direct2dDemo/Program.cs
@@ -0,0 +1,84 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Drawing;
+using System.Numerics;
+using Windows;
+using Windows.Win32.Foundation;
+using Windows.Win32.Graphics.Direct2D;
+
+namespace Direct2dDemo;
+
+internal class Program
+{
+ [STAThread]
+ private static void Main() => Application.Run(new Direct2dDemo());
+
+ private class Direct2dDemo : MainWindow
+ {
+ private SolidColorBrush? _lightSlateGrayBrush;
+ private SolidColorBrush? _cornflowerBlueBrush;
+
+ public Direct2dDemo() : base(title: "Simple Direct2D Application", features: Features.EnableDirect2d)
+ {
+ }
+
+ protected override void RenderTargetCreated(HwndRenderTarget renderTarget)
+ {
+ _lightSlateGrayBrush?.Dispose();
+ _cornflowerBlueBrush?.Dispose();
+ _lightSlateGrayBrush = renderTarget.CreateSolidColorBrush(Color.LightSlateGray);
+ _cornflowerBlueBrush = renderTarget.CreateSolidColorBrush(Color.CornflowerBlue);
+ base.RenderTargetCreated(renderTarget);
+ }
+
+ protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
+ {
+ switch (message)
+ {
+ case MessageType.Paint:
+ if (IsDirect2dEnabled(out var renderTarget))
+ {
+ renderTarget.SetTransform(Matrix3x2.Identity);
+ renderTarget.Clear(Color.White);
+
+ SizeF size = renderTarget.Size();
+
+ for (int x = 0; x < size.Width; x += 10)
+ {
+ renderTarget.DrawLine(
+ new(x, 0), new(x, size.Height),
+ _lightSlateGrayBrush!,
+ 0.5f);
+ }
+
+ for (int y = 0; y < size.Height; y += 10)
+ {
+ renderTarget.DrawLine(
+ new(0, y), new(size.Width, y),
+ _lightSlateGrayBrush!,
+ 0.5f);
+ }
+
+ RectangleF rectangle1 = RectangleF.FromLTRB(
+ size.Width / 2 - 50,
+ size.Height / 2 - 50,
+ size.Width / 2 + 50,
+ size.Height / 2 + 50);
+
+ RectangleF rectangle2 = RectangleF.FromLTRB(
+ size.Width / 2 - 100,
+ size.Height / 2 - 100,
+ size.Width / 2 + 100,
+ size.Height / 2 + 100);
+
+ renderTarget.FillRectangle(rectangle1, _lightSlateGrayBrush!);
+ renderTarget.DrawRectangle(rectangle2, _cornflowerBlueBrush!);
+ }
+ return (LRESULT)0;
+ }
+
+ return base.WindowProcedure(window, message, wParam, lParam);
+ }
+ }
+}
diff --git a/src/thirtytwo/MainWindow.cs b/src/thirtytwo/MainWindow.cs
index e6d43ed..d478ba9 100644
--- a/src/thirtytwo/MainWindow.cs
+++ b/src/thirtytwo/MainWindow.cs
@@ -18,7 +18,8 @@ public MainWindow(
WindowClass? windowClass = default,
nint parameters = default,
HMENU menuHandle = default,
- HBRUSH backgroundBrush = default) : base(
+ HBRUSH backgroundBrush = default,
+ Features features = default) : base(
bounds,
title,
style,
@@ -27,6 +28,7 @@ public MainWindow(
windowClass,
parameters,
menuHandle,
- backgroundBrush)
+ backgroundBrush,
+ features)
{ }
}
\ No newline at end of file
diff --git a/src/thirtytwo/NativeMethods.txt b/src/thirtytwo/NativeMethods.txt
index 48accd5..f7522bf 100644
--- a/src/thirtytwo/NativeMethods.txt
+++ b/src/thirtytwo/NativeMethods.txt
@@ -347,4 +347,7 @@ VIRTUAL_KEY
WIN32_ERROR
WINDOWPOS
WM_*
-XFORMCOORDS
\ No newline at end of file
+XFORMCOORDS
+ID2D1Factory
+D2D1CreateFactory
+D2DERR_RECREATE_TARGET
\ No newline at end of file
diff --git a/src/thirtytwo/Support/EnumExtensions.cs b/src/thirtytwo/Support/EnumExtensions.cs
new file mode 100644
index 0000000..d27b157
--- /dev/null
+++ b/src/thirtytwo/Support/EnumExtensions.cs
@@ -0,0 +1,82 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+namespace System;
+
+internal static class EnumExtensions
+{
+ ///
+ /// Returns true if the given flag or flags are set.
+ ///
+ ///
+ /// Simple wrapper for that gives you better intellisense.
+ ///
+ [SkipLocalsInit]
+ [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool AreFlagsSet(this T value, T flags) where T : unmanaged, Enum => value.HasFlag(flags);
+
+ ///
+ /// Sets the given flag or flags.
+ ///
+ [SkipLocalsInit]
+ [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ public static unsafe void SetFlags(this ref T value, T flags) where T : unmanaged, Enum
+ {
+ fixed (T* v = &value)
+ {
+ // Note that the non-relevant if clauses will be omitted by the JIT so these become one statement.
+ if (sizeof(T) == sizeof(byte))
+ {
+ *(byte*)v |= *(byte*)&flags;
+ }
+ else if (sizeof(T) == sizeof(short))
+ {
+ *(short*)v |= *(short*)&flags;
+ }
+ else if (sizeof(T) == sizeof(int))
+ {
+ *(int*)v |= *(int*)&flags;
+ }
+ else if (sizeof(T) == sizeof(long))
+ {
+ *(long*)v |= *(long*)&flags;
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+
+ [SkipLocalsInit]
+ [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ public static unsafe void ClearFlags(this ref T value, T flags) where T : unmanaged, Enum
+ {
+ fixed (T* v = &value)
+ {
+ // Note that the non-relevant if clauses will be omitted by the JIT so these become one statement.
+ if (sizeof(T) == sizeof(byte))
+ {
+ *(byte*)v &= (byte)~*(byte*)&flags;
+ }
+ else if (sizeof(T) == sizeof(short))
+ {
+ *(short*)v &= (short)~*(short*)&flags;
+ }
+ else if (sizeof(T) == sizeof(int))
+ {
+ *(int*)v &= ~*(int*)&flags;
+ }
+ else if (sizeof(T) == sizeof(long))
+ {
+ *(long*)v &= ~*(long*)&flags;
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/Brush.cs b/src/thirtytwo/Win32/Graphics/Direct2D/Brush.cs
new file mode 100644
index 0000000..38368e6
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/Brush.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Windows.Support;
+
+namespace Windows.Win32.Graphics.Direct2D;
+
+public unsafe class Brush : Resource, IPointer
+{
+ public unsafe new ID2D1Brush* Pointer { get; private set; }
+
+ public Brush(ID2D1Brush* brush) : base((ID2D1Resource*)brush) => Pointer = brush;
+
+ protected override void Dispose(bool disposing)
+ {
+ Pointer = null;
+ base.Dispose(disposing);
+ }
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/Common/D2D1_COLOR_F.cs b/src/thirtytwo/Win32/Graphics/Direct2D/Common/D2D1_COLOR_F.cs
new file mode 100644
index 0000000..01d845c
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/Common/D2D1_COLOR_F.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Drawing;
+
+namespace Windows.Win32.Graphics.Direct2D.Common;
+
+public partial struct D2D1_COLOR_F
+{
+ public D2D1_COLOR_F(float r, float g, float b, float a)
+ {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ this.a = a;
+ }
+
+ public static explicit operator D2D1_COLOR_F(Color value) =>
+ new(value.R / 255.0f, value.G / 255.0f, value.B / 255.0f, value.A / 255.0f);
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/Common/D2D_RECT_F.cs b/src/thirtytwo/Win32/Graphics/Direct2D/Common/D2D_RECT_F.cs
new file mode 100644
index 0000000..819aed7
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/Common/D2D_RECT_F.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Drawing;
+
+namespace Windows.Win32.Graphics.Direct2D.Common;
+
+public partial struct D2D_RECT_F
+{
+ public D2D_RECT_F(float left, float top, float right, float bottom)
+ {
+ this.left = left;
+ this.top = top;
+ this.right = right;
+ this.bottom = bottom;
+ }
+
+ public static implicit operator D2D_RECT_F(RectangleF value) =>
+ new(value.Left, value.Top, value.Right, value.Bottom);
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/Common/D2D_SIZE_U.cs b/src/thirtytwo/Win32/Graphics/Direct2D/Common/D2D_SIZE_U.cs
new file mode 100644
index 0000000..1d1bb2f
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/Common/D2D_SIZE_U.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Drawing;
+
+namespace Windows.Win32.Graphics.Direct2D.Common;
+
+public partial struct D2D_SIZE_U
+{
+ public D2D_SIZE_U(uint width, uint height)
+ {
+ this.width = width;
+ this.height = height;
+ }
+
+ public static explicit operator D2D_SIZE_U(Size value) =>
+ new(checked((uint)value.Width), checked((uint)value.Height));
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/Factory.cs b/src/thirtytwo/Win32/Graphics/Direct2D/Factory.cs
new file mode 100644
index 0000000..2303f6c
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/Factory.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Windows.Support;
+using Windows.Win32.System.Com;
+
+namespace Windows.Win32.Graphics.Direct2D;
+
+public unsafe class Factory : DisposableBase.Finalizable, IPointer
+{
+ private readonly AgileComPointer _factory;
+
+ public unsafe ID2D1Factory* Pointer { get; private set; }
+
+ public Factory(
+ D2D1_FACTORY_TYPE factoryType = D2D1_FACTORY_TYPE.D2D1_FACTORY_TYPE_SINGLE_THREADED,
+ D2D1_DEBUG_LEVEL factoryOptions = D2D1_DEBUG_LEVEL.D2D1_DEBUG_LEVEL_NONE)
+ {
+ ID2D1Factory* factory;
+ Interop.D2D1CreateFactory(
+ factoryType,
+ IID.Get(),
+ (D2D1_FACTORY_OPTIONS*)&factoryOptions,
+ (void**)&factory).ThrowOnFailure();
+
+ Pointer = factory;
+
+ // Ensure that this can be disposed on the finalizer thread by giving the "last" ref count
+ // to an agile pointer.
+ _factory = new AgileComPointer(factory, takeOwnership: true);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ Pointer = null;
+
+ if (disposing)
+ {
+ _factory.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/HwndRenderTarget.cs b/src/thirtytwo/Win32/Graphics/Direct2D/HwndRenderTarget.cs
new file mode 100644
index 0000000..33aec23
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/HwndRenderTarget.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Drawing;
+using Windows.Support;
+using Windows.Win32.Graphics.Direct2D.Common;
+
+namespace Windows.Win32.Graphics.Direct2D;
+
+public unsafe class HwndRenderTarget : RenderTarget, IPointer
+{
+ public new ID2D1HwndRenderTarget* Pointer { get; private set; }
+
+ public HwndRenderTarget(ID2D1HwndRenderTarget* renderTarget)
+ : base((ID2D1RenderTarget*)renderTarget)
+ {
+ Pointer = renderTarget;
+ }
+
+ public static HwndRenderTarget CreateForWindow(
+ TFactory factory,
+ TWindow window,
+ Size size)
+ where TFactory : IPointer
+ where TWindow : IHandle =>
+ CreateForWindow(
+ factory,
+ window,
+ new D2D_SIZE_U() { width = checked((uint)size.Width), height = checked((uint)size.Height) });
+
+ public static HwndRenderTarget CreateForWindow(
+ TFactory factory,
+ TWindow window,
+ D2D_SIZE_U size)
+ where TFactory : IPointer
+ where TWindow : IHandle
+ {
+ D2D1_RENDER_TARGET_PROPERTIES properties = default;
+ D2D1_HWND_RENDER_TARGET_PROPERTIES hwndProperties = new()
+ {
+ hwnd = window.Handle,
+ pixelSize = size
+ };
+
+ ID2D1HwndRenderTarget* renderTarget;
+ factory.Pointer->CreateHwndRenderTarget(
+ &properties,
+ &hwndProperties,
+ &renderTarget).ThrowOnFailure();
+
+ GC.KeepAlive(factory);
+ GC.KeepAlive(window.Wrapper);
+
+ return new HwndRenderTarget(renderTarget);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ Pointer = null;
+ base.Dispose(disposing);
+ }
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/ID2D1RenderTarget.cs b/src/thirtytwo/Win32/Graphics/Direct2D/ID2D1RenderTarget.cs
new file mode 100644
index 0000000..8927a71
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/ID2D1RenderTarget.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+using Windows.Win32.Graphics.Direct2D.Common;
+
+namespace Windows.Win32.Graphics.Direct2D;
+
+public unsafe partial struct ID2D1RenderTarget
+{
+ public D2D_SIZE_F GetSizeHack()
+ {
+ D2D_SIZE_F result;
+ return *((delegate* unmanaged)lpVtbl[53])((ID2D1RenderTarget*)Unsafe.AsPointer(ref this), &result);
+ }
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/RenderTarget.cs b/src/thirtytwo/Win32/Graphics/Direct2D/RenderTarget.cs
new file mode 100644
index 0000000..5cd8d4d
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/RenderTarget.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Windows.Support;
+
+namespace Windows.Win32.Graphics.Direct2D;
+
+public unsafe class RenderTarget : Resource, IPointer
+{
+ public unsafe new ID2D1RenderTarget* Pointer { get; private set; }
+
+ public RenderTarget(ID2D1RenderTarget* renderTarget) : base((ID2D1Resource*)renderTarget) => Pointer = renderTarget;
+
+ protected override void Dispose(bool disposing)
+ {
+ Pointer = null;
+ base.Dispose(disposing);
+ }
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/RenderTargetExtensions.cs b/src/thirtytwo/Win32/Graphics/Direct2D/RenderTargetExtensions.cs
new file mode 100644
index 0000000..f69b11a
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/RenderTargetExtensions.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Drawing;
+using System.Numerics;
+using Windows.Support;
+using Windows.Win32.Graphics.Direct2D.Common;
+
+namespace Windows.Win32.Graphics.Direct2D;
+
+public static unsafe class RenderTargetExtensions
+{
+ public static void Resize(this T target, Size size)
+ where T : IPointer
+ {
+ target.Pointer->Resize((D2D_SIZE_U)size).ThrowOnFailure();
+ GC.KeepAlive(target);
+ }
+
+ public static void BeginDraw(this T target) where T : IPointer
+ {
+ target.Pointer->BeginDraw();
+ GC.KeepAlive(target);
+ }
+
+ public static void EndDraw(this T target, out bool recreateTarget)
+ where T : IPointer
+ {
+ HRESULT result = target.Pointer->EndDraw(null, null);
+ if (result == HRESULT.D2DERR_RECREATE_TARGET)
+ {
+ recreateTarget = true;
+ }
+ else
+ {
+ result.ThrowOnFailure();
+ recreateTarget = false;
+ }
+
+ GC.KeepAlive(target);
+ }
+
+ public static SolidColorBrush CreateSolidColorBrush(this T target, Color color)
+ where T : IPointer
+ {
+ ID2D1SolidColorBrush* solidColorBrush;
+ D2D1_COLOR_F colorf = (D2D1_COLOR_F)color;
+ target.Pointer->CreateSolidColorBrush(&colorf, null, &solidColorBrush).ThrowOnFailure();
+ GC.KeepAlive(target);
+ return new SolidColorBrush(solidColorBrush);
+ }
+
+ public static void SetTransform(this T target, Matrix3x2 transform) where T : IPointer
+ {
+ target.Pointer->SetTransform((D2D_MATRIX_3X2_F*)&transform);
+ GC.KeepAlive(target);
+ }
+
+ public static void Clear(this T target, Color color) where T : IPointer
+ {
+ D2D1_COLOR_F colorf = (D2D1_COLOR_F)color;
+ target.Pointer->Clear(&colorf);
+ GC.KeepAlive(target);
+ }
+
+ public static void DrawLine(this T target, PointF point0, PointF point1, Brush brush, float strokeWidth = 1.0f)
+ where T : IPointer
+ {
+ target.Pointer->DrawLine(*(D2D_POINT_2F*)&point0, *(D2D_POINT_2F*)&point1, brush.Pointer, strokeWidth, null);
+ GC.KeepAlive(target);
+ }
+
+ public static void FillRectangle(this T target, RectangleF rect, Brush brush)
+ where T : IPointer
+ {
+ D2D_RECT_F rectf = (D2D_RECT_F)rect;
+ target.Pointer->FillRectangle(&rectf, brush.Pointer);
+ GC.KeepAlive(target);
+ }
+
+ public static void DrawRectangle(this T target, RectangleF rect, Brush brush, float strokeWidth = 1.0f)
+ where T : IPointer
+ {
+ D2D_RECT_F rectf = (D2D_RECT_F)rect;
+ target.Pointer->DrawRectangle(&rectf, brush.Pointer, strokeWidth, null);
+ GC.KeepAlive(target);
+ }
+
+ public static SizeF Size(this T target) where T : IPointer
+ {
+ D2D_SIZE_F size = target.Pointer->GetSizeHack();
+ GC.KeepAlive(target);
+ return *(SizeF*)&size;
+ }
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/Resource.cs b/src/thirtytwo/Win32/Graphics/Direct2D/Resource.cs
new file mode 100644
index 0000000..669fc3f
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/Resource.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Windows.Support;
+using Windows.Win32.System.Com;
+
+namespace Windows.Win32.Graphics.Direct2D;
+
+public unsafe class Resource : DisposableBase.Finalizable, IPointer
+{
+ private readonly AgileComPointer _resource;
+
+ public unsafe ID2D1Resource* Pointer { get; private set; }
+
+ public Resource(ID2D1Resource* resource)
+ {
+ Pointer = resource;
+
+ // Ensure that this can be disposed on the finalizer thread by giving the "last" ref count
+ // to an agile pointer.
+ _resource = new AgileComPointer(resource, takeOwnership: true);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ Pointer = null;
+
+ if (disposing)
+ {
+ _resource.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/Direct2D/SolidColorBrush.cs b/src/thirtytwo/Win32/Graphics/Direct2D/SolidColorBrush.cs
new file mode 100644
index 0000000..1d097f1
--- /dev/null
+++ b/src/thirtytwo/Win32/Graphics/Direct2D/SolidColorBrush.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Jeremy W. Kuhne. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Windows.Support;
+
+namespace Windows.Win32.Graphics.Direct2D;
+
+public unsafe class SolidColorBrush : Brush, IPointer
+{
+ public unsafe new ID2D1SolidColorBrush* Pointer { get; private set; }
+
+ public SolidColorBrush(ID2D1SolidColorBrush* brush) : base((ID2D1Brush*)brush) => Pointer = brush;
+
+ protected override void Dispose(bool disposing)
+ {
+ Pointer = null;
+ base.Dispose(disposing);
+ }
+}
\ No newline at end of file
diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/Session.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/Session.cs
index 4d3bd23..9220dc8 100644
--- a/src/thirtytwo/Win32/Graphics/GdiPlus/Session.cs
+++ b/src/thirtytwo/Win32/Graphics/GdiPlus/Session.cs
@@ -1,9 +1,6 @@
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using System.Runtime.CompilerServices;
-using Windows.Support;
-
namespace Windows.Win32.Graphics.GdiPlus;
public class Session : IDisposable
diff --git a/src/thirtytwo/Window.cs b/src/thirtytwo/Window.cs
index 12a374a..6497aa5 100644
--- a/src/thirtytwo/Window.cs
+++ b/src/thirtytwo/Window.cs
@@ -6,20 +6,32 @@
using System.Numerics;
using Windows.Components;
using Windows.Support;
+using Windows.Win32.Graphics.Direct2D;
namespace Windows;
public unsafe class Window : ComponentBase, IHandle, ILayoutHandler
{
+ private static readonly object s_lock = new();
+ private static readonly ConcurrentDictionary> s_windows = new();
+ private static readonly WindowClass s_defaultWindowClass = new(className: $"DefaultWindowClass_{Guid.NewGuid()}");
+ protected static Factory? Direct2dFactory { get; private set; }
+ public static Rectangle DefaultBounds { get; }
+ = new(Interop.CW_USEDEFAULT, Interop.CW_USEDEFAULT, Interop.CW_USEDEFAULT, Interop.CW_USEDEFAULT);
+
+ // Default fonts for each DPI
+ private static readonly ConcurrentDictionary s_defaultFonts = new();
+ internal static WNDPROC DefaultWindowProcedure { get; } = GetDefaultWindowProcedure();
+
// High precision metric units are .01mm each
private const int HiMetricUnitsPerInch = 2540;
+ private readonly object _lock = new();
+
// Stash the delegate to keep it from being collected
private readonly WindowProcedure _windowProcedure;
private readonly WNDPROC _priorWindowProcedure;
private readonly WindowClass _windowClass;
- private static readonly object s_lock = new();
- private readonly object _lock = new();
private bool _destroyed;
private HWND _handle;
@@ -30,28 +42,40 @@ public unsafe class Window : ComponentBase, IHandle, ILayoutHandler
// https://devblogs.microsoft.com/oldnewthing/20080912-00/?p=20893
private HFONT _font;
+ private HFONT _lastCreatedFont;
private readonly HBRUSH _backgroundBrush;
- private static readonly WindowClass s_defaultWindowClass = new(className: $"DefaultWindowClass_{Guid.NewGuid()}");
- internal static WNDPROC DefaultWindowProcedure { get; } = GetDefaultWindowProcedure();
+ protected HwndRenderTarget? Direct2dRenderTarget { get; private set; }
private string? _text;
private uint _lastDpi;
- private static readonly ConcurrentDictionary> s_windows = new();
- private HFONT _lastCreatedFont;
+ private readonly Features _features;
- // Default fonts for each DPI
- private static readonly ConcurrentDictionary s_defaultFonts = new();
+ [MemberNotNullWhen(true, nameof(Direct2dRenderTarget))]
+ protected bool IsDirect2dEnabled()
+ {
+ bool enabled = _features.AreFlagsSet(Features.EnableDirect2d);
+ if (enabled && Direct2dRenderTarget is null)
+ {
+ UpdateRenderTarget(Handle, this.GetClientRectangle().Size);
+ }
- public static Rectangle DefaultBounds { get; }
- = new(Interop.CW_USEDEFAULT, Interop.CW_USEDEFAULT, Interop.CW_USEDEFAULT, Interop.CW_USEDEFAULT);
+ return enabled;
+ }
- ///
- /// The window handle. This will be after the window is destroyed.
- ///
- public HWND Handle => _handle;
+ [MemberNotNullWhen(true, nameof(Direct2dRenderTarget))]
+ protected bool IsDirect2dEnabled([NotNullWhen(true)] out HwndRenderTarget? renderTarget)
+ {
+ renderTarget = Direct2dRenderTarget;
+ return IsDirect2dEnabled();
+ }
+
+///
+/// The window handle. This will be after the window is destroyed.
+///
+public HWND Handle => _handle;
public event WindowsMessageEvent? MessageHandler;
@@ -64,7 +88,8 @@ public Window(
WindowClass? windowClass = default,
nint parameters = default,
HMENU menuHandle = default,
- HBRUSH backgroundBrush = default)
+ HBRUSH backgroundBrush = default,
+ Features features = default)
{
_windowClass = windowClass ?? s_defaultWindowClass;
@@ -74,6 +99,12 @@ public Window(
}
_text = text;
+ _features = features;
+
+ if (_features.AreFlagsSet(Features.EnableDirect2d))
+ {
+ Direct2dFactory ??= new();
+ }
try
{
@@ -160,6 +191,32 @@ public void SetFont(string typeFace, int pointSize)
this.SetFontHandle(_lastCreatedFont);
}
+ [MemberNotNull(nameof(Direct2dRenderTarget))]
+ private void UpdateRenderTarget(HWND window, Size size)
+ {
+ if (Direct2dFactory is null)
+ {
+ throw new InvalidOperationException("Should never call UpdateRenderTarget without a factory.");
+ }
+
+ if (Direct2dRenderTarget is null)
+ {
+ Direct2dRenderTarget = HwndRenderTarget.CreateForWindow(Direct2dFactory, window, size);
+ RenderTargetCreated(Direct2dRenderTarget);
+ }
+ else
+ {
+ Direct2dRenderTarget.Resize(size);
+ }
+ }
+
+ ///
+ /// Called whenever the Direct2D render target has been created or recreated.
+ ///
+ protected virtual void RenderTargetCreated(HwndRenderTarget renderTarget)
+ {
+ }
+
private LRESULT InitializationWindowProcedure(HWND window, uint message, WPARAM wParam, LPARAM lParam)
{
if (Handle.IsNull)
@@ -175,6 +232,43 @@ private LRESULT InitializationWindowProcedure(HWND window, uint message, WPARAM
private LRESULT WindowProcedureInternal(HWND window, uint message, WPARAM wParam, LPARAM lParam)
{
+ // What is the difference between WM_DESTROY and WM_NCDESTROY?
+ // https://devblogs.microsoft.com/oldnewthing/20050726-00/?p=34803
+
+ switch (message)
+ {
+ case Interop.WM_NCDESTROY:
+ lock (_lock)
+ {
+ // This should be the final message. Track that we've been destroyed so we know we don't have
+ // to manually clean up.
+
+ bool success = s_windows.TryRemove(Handle, out _);
+ Debug.Assert(success);
+ _handle = default;
+ _destroyed = true;
+ }
+
+ break;
+
+ case Interop.WM_SIZE:
+ // Check the flag directly here so we don't create then resize.
+ if (_features.AreFlagsSet(Features.EnableDirect2d))
+ {
+ UpdateRenderTarget(window, new Size(lParam.LOWORD, lParam.HIWORD));
+ }
+
+ break;
+
+ case Interop.WM_PAINT:
+ if (IsDirect2dEnabled(out var renderTarget))
+ {
+ renderTarget.BeginDraw();
+ }
+
+ break;
+ }
+
if (MessageHandler is { } handlers)
{
foreach (var handler in handlers.GetInvocationList().OfType())
@@ -187,25 +281,19 @@ private LRESULT WindowProcedureInternal(HWND window, uint message, WPARAM wParam
}
}
- // What is the difference between WM_DESTROY and WM_NCDESTROY?
- // https://devblogs.microsoft.com/oldnewthing/20050726-00/?p=34803
+ LRESULT windProcResult = WindowProcedure(window, (MessageType)message, wParam, lParam);
- if ((MessageType)message == MessageType.NonClientDestroy)
+ if (message == Interop.WM_PAINT && IsDirect2dEnabled())
{
- lock (_lock)
+ Direct2dRenderTarget.EndDraw(out bool recreateTarget);
+ if (recreateTarget)
{
- // This should be the final message. Track that we've been destroyed so we know we don't have
- // to manually clean up.
-
- bool success = s_windows.TryRemove(Handle, out _);
- Debug.Assert(success);
- _handle = default;
- _destroyed = true;
+ Direct2dRenderTarget.Dispose();
+ Direct2dRenderTarget = null;
+ UpdateRenderTarget(window, this.GetClientRectangle().Size);
}
}
- LRESULT windProcResult = WindowProcedure(window, (MessageType)message, wParam, lParam);
-
// Ensure we're not collected while we're processing a message.
GC.KeepAlive(this);
return windProcResult;
@@ -232,6 +320,12 @@ protected virtual LRESULT WindowProcedure(HWND window, MessageType message, WPAR
return (LRESULT)1;
}
+ if (IsDirect2dEnabled())
+ {
+ // Having the HDC erased will cause flicker, so say we handled it.
+ return (LRESULT)1;
+ }
+
break;
case MessageType.GetFont:
@@ -442,4 +536,13 @@ private static WNDPROC GetDefaultWindowProcedure()
Debug.Assert(!address.IsNull);
return (WNDPROC)(void*)address.Value;
}
+
+ [Flags]
+ public enum Features
+ {
+ ///
+ /// Set this flag to enable Direct2D rendering.
+ ///
+ EnableDirect2d = 0b0000_0000_0000_0000_0000_0000_0000_0001,
+ }
}
\ No newline at end of file
diff --git a/thirtytwo.sln b/thirtytwo.sln
index d11c483..5dd6b4e 100644
--- a/thirtytwo.sln
+++ b/thirtytwo.sln
@@ -55,6 +55,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Checker", "src\samples\Petz
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clock", "src\samples\Petzold\5th\Clock\Clock.csproj", "{D4EA97FD-45D3-4A26-843F-63427F0F1BFE}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DirectDraw", "DirectDraw", "{7107A105-51E2-46EB-859D-E2DD2FD51839}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Direct2dDemo", "src\samples\DirectDraw\Direct2dDemo\Direct2dDemo.csproj", "{1EC566E8-C337-42FF-9BA4-DB66BFAE5B8A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -137,6 +141,10 @@ Global
{D4EA97FD-45D3-4A26-843F-63427F0F1BFE}.Debug|x64.Build.0 = Debug|x64
{D4EA97FD-45D3-4A26-843F-63427F0F1BFE}.Release|x64.ActiveCfg = Release|x64
{D4EA97FD-45D3-4A26-843F-63427F0F1BFE}.Release|x64.Build.0 = Release|x64
+ {1EC566E8-C337-42FF-9BA4-DB66BFAE5B8A}.Debug|x64.ActiveCfg = Debug|x64
+ {1EC566E8-C337-42FF-9BA4-DB66BFAE5B8A}.Debug|x64.Build.0 = Debug|x64
+ {1EC566E8-C337-42FF-9BA4-DB66BFAE5B8A}.Release|x64.ActiveCfg = Release|x64
+ {1EC566E8-C337-42FF-9BA4-DB66BFAE5B8A}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -160,6 +168,8 @@ Global
{22E8534C-628A-4A9C-9EDB-7BE4DAC12544} = {13110246-EBE1-441B-B721-B0614D62B13B}
{7DB41BCA-E497-4EE4-9A2F-A529E5306ABC} = {13110246-EBE1-441B-B721-B0614D62B13B}
{D4EA97FD-45D3-4A26-843F-63427F0F1BFE} = {13110246-EBE1-441B-B721-B0614D62B13B}
+ {7107A105-51E2-46EB-859D-E2DD2FD51839} = {C059056C-D771-4CF5-A93F-AA7140FF7827}
+ {1EC566E8-C337-42FF-9BA4-DB66BFAE5B8A} = {7107A105-51E2-46EB-859D-E2DD2FD51839}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3761BFC9-DBEF-4186-BB8B-BC0D84ED9AE5}