Skip to content

Instantly share code, notes, and snippets.

@smourier
Last active April 27, 2026 04:48
Show Gist options
  • Select an option

  • Save smourier/ecde006af16f0f797b0085d3de6d6cfd to your computer and use it in GitHub Desktop.

Select an option

Save smourier/ecde006af16f0f797b0085d3de6d6cfd to your computer and use it in GitHub Desktop.
Using Direct3D11 and Direct2D in WPF's D3DImage
<Window
x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp2"
xmlns:wpfInterop="clr-namespace:System.Windows.Interop;assembly=PresentationCore"
Title="WpfApp2">
<Grid>
<Button
Width="200"
Height="80"
Margin="0,200,0,0"
VerticalAlignment="Top"
Content="WPF Button" />
<Image Name="ImageHost" Stretch="Fill">
<Image.Source>
<wpfInterop:D3DImage x:Name="TheD3dImage" />
</Image.Source>
</Image>
<Button
Width="200"
Height="80"
Margin="0,0,0,200"
VerticalAlignment="Bottom"
Content="WPF Button" />
</Grid>
</Window>
using System;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using DirectN;
using DirectN.Extensions;
using DirectN.Extensions.Com;
namespace WpfApp2;
public partial class MainWindow : Window
{
private readonly IComObject<ID3D11Device> _d3d11Device;
private readonly IComObject<ID3D11DeviceContext> _d3d11DeviceContext;
private readonly IComObject<ID2D1Factory> _d2dFactory;
private ComObject<IDirect3D9Ex> _d3d9 = null!;
private ComObject<IDirect3DDevice9Ex> _d3d9Device = null!;
private IComObject<ID3D11Texture2D> _d3d11Texture = null!;
private ComObject<IDirect3DTexture9> _d3d9Texture = null!;
private ComObject<IDirect3DSurface9> _d3d9Surface = null!;
private IComObject<ID2D1RenderTarget> _d2dRenderTarget = null!;
private Hsv _colorValue = new(0, 1, 1);
public MainWindow()
{
InitializeComponent();
TheD3dImage.IsFrontBufferAvailableChanged += OnFrontBufferAvailableChanged;
_d3d11Device = D3D11Functions.D3D11CreateDevice(
null,
D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE,
D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_DEBUG,
out _d3d11DeviceContext);
_d2dFactory = D2D1Functions.D2D1CreateFactory(D2D1_FACTORY_TYPE.D2D1_FACTORY_TYPE_SINGLE_THREADED, new D2D1_FACTORY_OPTIONS { debugLevel = D2D1_DEBUG_LEVEL.D2D1_DEBUG_LEVEL_INFORMATION });
Loaded += OnLoaded;
}
protected override void OnClosed(EventArgs e)
{
_d2dRenderTarget?.Dispose();
_d3d9Surface?.Dispose();
_d3d9Texture?.Dispose();
_d3d11Texture?.Dispose();
_d3d9Device?.Dispose();
_d3d9?.Dispose();
_d2dFactory?.Dispose();
_d3d11DeviceContext?.Dispose();
_d3d11Device?.Dispose();
base.OnClosed(e);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var handle = new WindowInteropHelper(this).Handle;
CreateD3d9Device(handle);
(uint deviceWidth, uint deviceHeight) = GetDeviceSize(this);
CreateSharedD3d11Texture2d(deviceWidth, deviceHeight);
SetBackBuffer();
CreateD2dRenderTarget();
CompositionTarget.Rendering += OnCompositionTargetRendering;
}
private void OnFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TheD3dImage.Lock();
if (TheD3dImage.IsFrontBufferAvailable)
{
ComObject.WithComInstance(_d3d9Surface, unk =>
{
TheD3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, unk);
});
}
TheD3dImage.Unlock();
}
private void OnCompositionTargetRendering(object? sender, EventArgs e)
{
if (!TheD3dImage.IsFrontBufferAvailable)
return;
_colorValue.Hue += 5;
TheD3dImage.Lock();
_d2dRenderTarget.BeginDraw();
_d2dRenderTarget.Clear(_colorValue.ToD3DCOLORVALUE());
_d2dRenderTarget.EndDraw();
_d3d11DeviceContext.Flush();
TheD3dImage.AddDirtyRect(new Int32Rect(0, 0, TheD3dImage.PixelWidth, TheD3dImage.PixelHeight));
TheD3dImage.Unlock();
}
private void CreateD3d9Device(nint handle)
{
Functions.Direct3DCreate9Ex(Constants.D3D_SDK_VERSION, out var d3d9Obj).ThrowOnError();
_d3d9 = new ComObject<IDirect3D9Ex>(d3d9Obj);
var pp = new D3DPRESENT_PARAMETERS()
{
Windowed = true,
SwapEffect = D3DSWAPEFFECT.D3DSWAPEFFECT_DISCARD,
hDeviceWindow = handle,
PresentationInterval = unchecked((uint)Constants.D3DPRESENT_INTERVAL_IMMEDIATE),
};
var flags = Constants.D3DCREATE_HARDWARE_VERTEXPROCESSING;
_d3d9.Object.CreateDeviceEx(Constants.D3DADAPTER_DEFAULT, D3DDEVTYPE.D3DDEVTYPE_HAL, handle, (uint)flags, ref pp, ref Unsafe.NullRef<D3DDISPLAYMODEEX>(), out var device).ThrowOnError();
_d3d9Device = new ComObject<IDirect3DDevice9Ex>(device);
}
private static (uint deviceWidth, uint deviceHeight) GetDeviceSize(FrameworkElement frameworkElement)
{
var dpiScale = VisualTreeHelper.GetDpi(frameworkElement);
var deviceWidth = (uint)(frameworkElement.ActualWidth * dpiScale.DpiScaleX);
var deviceHeight = (uint)(frameworkElement.ActualHeight * dpiScale.DpiScaleY);
return (deviceWidth, deviceHeight);
}
private void CreateSharedD3d11Texture2d(uint width, uint height)
{
var textureDescription = new D3D11_TEXTURE2D_DESC()
{
ArraySize = 1,
Format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
Width = width,
Height = height,
MipLevels = 1,
SampleDesc = new DXGI_SAMPLE_DESC() { Count = 1 },
BindFlags = (uint)D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET,
MiscFlags = (uint)D3D11_RESOURCE_MISC_FLAG.D3D11_RESOURCE_MISC_SHARED,
};
_d3d11Texture = _d3d11Device.CreateTexture2D(textureDescription);
}
private void SetBackBuffer()
{
CreateSharedD3d9Texture();
_d3d9Texture.Object.GetSurfaceLevel(0, out var levelObj).ThrowOnError();
_d3d9Surface = new ComObject<IDirect3DSurface9>(levelObj);
ComObject.WithComInstance(_d3d9Surface, unk =>
{
TheD3dImage.Lock();
TheD3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, unk);
TheD3dImage.Unlock();
});
}
private void CreateSharedD3d9Texture()
{
var res = _d3d11Texture.As<IDXGIResource>()!;
res.Object.GetSharedHandle(out var handle).ThrowOnError();
var tex = _d3d11Texture.GetDesc();
_d3d9Device!.Object.CreateTexture(tex.Width, tex.Height, 1, Constants.D3DUSAGE_RENDERTARGET, D3DFORMAT.D3DFMT_A8R8G8B8, D3DPOOL.D3DPOOL_DEFAULT, out var texObj, ref handle).ThrowOnError();
_d3d9Texture = new ComObject<IDirect3DTexture9>(texObj)!;
}
private void CreateD2dRenderTarget()
{
var dxgiSurface = _d3d11Texture.As<IDXGISurface>()!;
var props = new D2D1_RENDER_TARGET_PROPERTIES
{
pixelFormat = new D2D1_PIXEL_FORMAT() { format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, alphaMode = D2D1_ALPHA_MODE.D2D1_ALPHA_MODE_PREMULTIPLIED }
};
_d2dRenderTarget = _d2dFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, props);
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DirectNAot" Version="1.5.2" />
<PackageReference Include="DirectNAot.Extensions" Version="1.5.2" />
</ItemGroup>
</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment