Last active
April 27, 2026 04:48
-
-
Save smourier/ecde006af16f0f797b0085d3de6d6cfd to your computer and use it in GitHub Desktop.
Using Direct3D11 and Direct2D in WPF's D3DImage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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