Skip to content

Instantly share code, notes, and snippets.

@pjmagee
Created November 15, 2025 19:32
Show Gist options
  • Select an option

  • Save pjmagee/af673fd5d3b54926493d81bf9dcdae58 to your computer and use it in GitHub Desktop.

Select an option

Save pjmagee/af673fd5d3b54926493d81bf9dcdae58 to your computer and use it in GitHub Desktop.
using System.Runtime.InteropServices;
using Silk.NET.Core.Native;
using Silk.NET.Direct2D;
using Silk.NET.Maths;
using Silk.NET.DXGI;
using D2DAlphaMode = Silk.NET.Direct2D.AlphaMode;
using D2DFeatureLevel = Silk.NET.Direct2D.FeatureLevel;
unsafe class Program
{
// Win32 API constants
private const int WS_OVERLAPPEDWINDOW = 0x00CF0000;
private const int WS_VISIBLE = 0x10000000;
private const int CW_USEDEFAULT = unchecked((int)0x80000000);
private const int SW_SHOW = 5;
private const uint WM_DESTROY = 0x0002;
private const uint WM_PAINT = 0x000F;
private const uint WM_SIZE = 0x0005;
private const uint CS_HREDRAW = 0x0002;
private const uint CS_VREDRAW = 0x0001;
private const uint IDC_ARROW = 32512;
private const uint COLOR_WINDOW = 5;
// Win32 API structures
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct WNDCLASSEXW
{
public uint cbSize;
public uint style;
public IntPtr lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
public IntPtr lpszMenuName;
public IntPtr lpszClassName;
public IntPtr hIconSm;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSG
{
public IntPtr hwnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public POINT pt;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct PAINTSTRUCT
{
public IntPtr hdc;
public bool fErase;
public RECT rcPaint;
public bool fRestore;
public bool fIncUpdate;
[MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 32)]
public byte[] rgbReserved;
}
// Win32 API imports
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern ushort RegisterClassExW(ref WNDCLASSEXW lpwcx);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr CreateWindowExW(
uint dwExStyle,
IntPtr lpClassName,
string lpWindowName,
uint dwStyle,
int x, int y,
int nWidth, int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
private static extern bool UpdateWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern sbyte GetMessageW(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll")]
private static extern bool TranslateMessage(ref MSG lpMsg);
[DllImport("user32.dll")]
private static extern IntPtr DispatchMessageW(ref MSG lpMsg);
[DllImport("user32.dll")]
private static extern void PostQuitMessage(int nExitCode);
[DllImport("user32.dll")]
private static extern IntPtr DefWindowProcW(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr GetModuleHandleW(string? lpModuleName);
[DllImport("user32.dll")]
private static extern IntPtr LoadCursorW(IntPtr hInstance, IntPtr lpCursorName);
[DllImport("user32.dll")]
private static extern IntPtr BeginPaint(IntPtr hWnd, out PAINTSTRUCT lpPaint);
[DllImport("user32.dll")]
private static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
[DllImport("user32.dll")]
private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
private delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
// Direct2D objects
private static ID2D1Factory* d2dFactory;
private static ID2D1HwndRenderTarget* renderTarget;
private static ID2D1SolidColorBrush* brush;
private static IntPtr hwnd;
private static D2D d2d = null!;
static void Main()
{
// Initialize Direct2D
d2d = D2D.GetApi();
IntPtr hInstance = GetModuleHandleW(null);
// Create window class name
string className = "Direct2DWindowClass";
IntPtr classNamePtr = Marshal.StringToHGlobalUni(className);
// Register window class
WndProc wndProcDelegate = WindowProc;
IntPtr wndProcPtr = Marshal.GetFunctionPointerForDelegate(wndProcDelegate);
WNDCLASSEXW wc = new WNDCLASSEXW
{
cbSize = (uint)Marshal.SizeOf<WNDCLASSEXW>(),
style = CS_HREDRAW | CS_VREDRAW,
lpfnWndProc = wndProcPtr,
cbClsExtra = 0,
cbWndExtra = 0,
hInstance = hInstance,
hIcon = IntPtr.Zero,
hCursor = LoadCursorW(IntPtr.Zero, new IntPtr(IDC_ARROW)),
hbrBackground = new IntPtr(COLOR_WINDOW + 1),
lpszMenuName = IntPtr.Zero,
lpszClassName = classNamePtr,
hIconSm = IntPtr.Zero
};
if (RegisterClassExW(ref wc) == 0)
{
Console.WriteLine("Failed to register window class");
return;
}
// Create window (500x500)
hwnd = CreateWindowExW(
0,
classNamePtr,
"Direct2D Simple Rendering",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
500, 500,
IntPtr.Zero,
IntPtr.Zero,
hInstance,
IntPtr.Zero);
if (hwnd == IntPtr.Zero)
{
Console.WriteLine("Failed to create window");
return;
}
// Initialize Direct2D factory
InitializeDirect2D();
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
// Message loop
MSG msg;
while (GetMessageW(out msg, IntPtr.Zero, 0, 0) > 0)
{
TranslateMessage(ref msg);
DispatchMessageW(ref msg);
}
// Cleanup
Cleanup();
Marshal.FreeHGlobal(classNamePtr);
GC.KeepAlive(wndProcDelegate);
}
private static void InitializeDirect2D()
{
// Create D2D factory
Guid iid = typeof(ID2D1Factory).GUID;
fixed (ID2D1Factory** ppFactory = &d2dFactory)
{
d2d.D2D1CreateFactory(FactoryType.SingleThreaded, &iid, null, (void**)ppFactory);
}
}
private static void CreateRenderTarget()
{
if (renderTarget != null)
return;
// Get client rect
RECT rc;
GetClientRect(hwnd, out rc);
var size = new Silk.NET.Maths.Vector2D<uint>((uint)(rc.Right - rc.Left), (uint)(rc.Bottom - rc.Top));
var renderTargetProperties = new RenderTargetProperties
{
Type = RenderTargetType.Default,
PixelFormat = new PixelFormat
{
Format = Format.FormatB8G8R8A8Unorm,
AlphaMode = D2DAlphaMode.Premultiplied
},
DpiX = 0,
DpiY = 0,
Usage = RenderTargetUsage.None,
MinLevel = D2DFeatureLevel.Level10
};
var hwndRenderTargetProperties = new HwndRenderTargetProperties
{
Hwnd = new nint(hwnd),
PixelSize = size,
PresentOptions = PresentOptions.None
};
fixed (ID2D1HwndRenderTarget** ppRenderTarget = &renderTarget)
{
d2dFactory->CreateHwndRenderTarget(&renderTargetProperties, &hwndRenderTargetProperties, ppRenderTarget);
}
// Create a solid color brush (blue)
var color = new D3Dcolorvalue
{
R = 0.0f,
G = 0.5f,
B = 1.0f,
A = 1.0f
};
fixed (ID2D1SolidColorBrush** ppBrush = &brush)
{
((ID2D1RenderTarget*)renderTarget)->CreateSolidColorBrush(&color, null, ppBrush);
}
}
private static void Render()
{
if (renderTarget == null)
CreateRenderTarget();
if (renderTarget == null)
return;
var baseRenderTarget = (ID2D1RenderTarget*)renderTarget;
baseRenderTarget->BeginDraw();
// Clear background to white
var clearColor = new D3Dcolorvalue
{
R = 1.0f,
G = 1.0f,
B = 1.0f,
A = 1.0f
};
baseRenderTarget->Clear(&clearColor);
// Draw a simple square in the center (200x200 pixels)
var rect = new Silk.NET.Maths.Box2D<float>(150, 150, 350, 350);
baseRenderTarget->FillRectangle(&rect, (ID2D1Brush*)brush);
ulong tag1, tag2;
baseRenderTarget->EndDraw(&tag1, &tag2);
}
private static IntPtr WindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
switch (msg)
{
case WM_PAINT:
PAINTSTRUCT ps;
BeginPaint(hWnd, out ps);
Render();
EndPaint(hWnd, ref ps);
return IntPtr.Zero;
case WM_SIZE:
if (renderTarget != null)
{
RECT rc;
GetClientRect(hWnd, out rc);
var size = new Silk.NET.Maths.Vector2D<uint>((uint)(rc.Right - rc.Left), (uint)(rc.Bottom - rc.Top));
renderTarget->Resize(&size);
}
return IntPtr.Zero;
case WM_DESTROY:
PostQuitMessage(0);
return IntPtr.Zero;
}
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
private static void Cleanup()
{
if (brush != null)
{
brush->Release();
brush = null;
}
if (renderTarget != null)
{
renderTarget->Release();
renderTarget = null;
}
if (d2dFactory != null)
{
d2dFactory->Release();
d2dFactory = null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment