WPF中使用Direct2D遇参数错误:CreateTexture调用失败排查
我正在使用Vortice在WPF窗口中绘制Direct2D图形。此前通过Vortice.Direct2D1.ID2D1Factory8.CreateHwndRenderTarget()实现功能,但因依赖HWND存在空域(airspace)问题,故尝试使用无空域问题的System.Windows.Interop.D3DImage。
注:此举一是作为技术练习,二是需实现低CPU占用的滚动图表控件——WPF无滚动渲染机制,每次需重绘全部内容,而Direct2D支持滚动时仅渲染新增区域。
D3DImage.SetBackBuffer()仅接受IDirect3DSurface9,因此需要通过Direct3D11、DXGI和共享句柄(share handles)实现Direct2D绘制到Direct3D9表面,步骤如下:
- 创建共享的
Vortice.Direct3D11.ID3D11Texture2D(已成功获取非空共享句柄); - 将其转换为
Vortice.DXGI.IDXGISurface(成功); - 通过
Vortice.Direct2D1.ID2D1Factory.CreateDxgiSurfaceRenderTarget()创建ID2D1RenderTarget(成功); - 通过
Vortice.Direct3D9.IDirect3DDevice9Ex.CreateTexture()传入共享句柄打开IDirect3DTexture9(此处失败); - 将
IDirect3DTexture9设为D3DImage的后台缓冲区; - 后续向
ID2D1RenderTarget绘制图形,同步到D3DImage。
调用CreateTexture()时抛出SharpGen.Runtime.SharpGenException,错误信息为:HRESULT: [0x80070057], Module: [Vortice.Direct2D1], ApiCode: [WINCODEC_ERR_INVALIDPARAMETER/InvalidParameter], Message: [参数不正确]。若将共享句柄设为0则不报错,但无法渲染内容。
最小可复现代码
Experiment.Direct3dImageEx.csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net9.0-windows</TargetFramework> <LangVersion>13</LangVersion> <OutputType>WinExe</OutputType> <Configurations>Debug;Release</Configurations> <UseWPF>true</UseWPF> </PropertyGroup> <ItemGroup> <PackageReference Include="Vortice.Direct2D1" Version="3.8.3" /> <PackageReference Include="Vortice.Direct3D9" Version="3.8.3" /> <PackageReference Include="Vortice.Direct3D11" Version="3.8.3" /> </ItemGroup> </Project>
MainWindow.xaml
<Window x:Class="Experiment.Direct3dImageEx.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpfInterop="clr-namespace:System.Windows.Interop;assembly=PresentationCore" xmlns:local="clr-namespace:Experiment.Direct3dImageEx" Title="Experiment.Direct3dImageEx"> <Grid> <Button Width="200" Height="80" Content="WPF Button" VerticalAlignment="Top" Margin="0,200,0,0"/> <Image Stretch="Fill" Name="ImageHost"> <Image.Source> <wpfInterop:D3DImage x:Name="TheD3dImage"/> </Image.Source> </Image> <Button Width="200" Content="WPF Button" Height="80" VerticalAlignment="Bottom" Margin="0,0,0,200"/> </Grid> </Window>
MainWindow.xaml.cs
namespace Experiment.Direct3dImageEx; using Sys = System; using Wpf = System.Windows; using WpfMedia = System.Windows.Media; using WpfInterop = System.Windows.Interop; using D2d = Vortice.Direct2D1; using D3d = Vortice.Direct3D; using D3d9 = Vortice.Direct3D9; using D3d11 = Vortice.Direct3D11; using DCommon = Vortice.DCommon; using Dxgi = Vortice.DXGI; public partial class MainWindow : Wpf.Window { static readonly D2d.ID2D1Factory8 d2dFactory = D2d.D2D1.D2D1CreateFactory<D2d.ID2D1Factory8>(); static readonly D3d9.IDirect3D9Ex d3d9 = D3d9.D3D9.Direct3DCreate9Ex(); static readonly D3d11.ID3D11Device d3d11Device = D3d11.D3D11.D3D11CreateDevice( D3d.DriverType.Hardware, D3d11.DeviceCreationFlags.BgraSupport ); D3d9.IDirect3DDevice9Ex? d3d9Device; D3d11.ID3D11Texture2D? d3d11Texture2d; D2d.ID2D1RenderTarget? d2dRenderTarget; float colorValue; public MainWindow() { InitializeComponent(); Loaded += onLoaded; } void onLoaded( object sender, Wpf.RoutedEventArgs e ) { nint deviceWindowHandle = new WpfInterop.WindowInteropHelper( this ).Handle; d3d9Device = createD3d9Device( deviceWindowHandle ); (uint deviceWidth, uint deviceHeight) = getDeviceSize( this ); d3d11Texture2d = createSharedD3d11Texture2d( Dxgi.Format.B8G8R8A8_UNorm, deviceWidth, deviceHeight ); setBackBuffer( d3d11Texture2d ); d2dRenderTarget = createD2dRenderTarget( d3d11Texture2d ); WpfMedia.CompositionTarget.Rendering += onCompositionTargetRendering; } void setBackBuffer( D3d11.ID3D11Texture2D d3d11Texture2d ) { D3d9.IDirect3DTexture9 d3d9Texture = getSharedD3d9Texture( d3d9Device!, d3d11Texture2d ); D3d9.IDirect3DSurface9 d3d9Surface = d3d9Texture.GetSurfaceLevel( 0 ); TheD3dImage.Lock(); TheD3dImage.SetBackBuffer( WpfInterop.D3DResourceType.IDirect3DSurface9, d3d9Surface.NativePointer ); TheD3dImage.Unlock(); } static D3d9.IDirect3DTexture9 getSharedD3d9Texture( D3d9.IDirect3DDevice9Ex d3d9Device, D3d11.ID3D11Texture2D d3d11Texture2d ) { D3d11.Texture2DDescription textureDescription = d3d11Texture2d.Description; uint width = textureDescription.Width; uint height = textureDescription.Height; D3d9.Format d3d9Format = d3d9FormatFromDxgi( textureDescription.Format ); nint sharedHandle = d3d11Texture2d.QueryInterface<Dxgi.IDXGIResource>().SharedHandle; assert( sharedHandle != 0 ); // sharedHandle = 0; // this prevents failure but of course nothing renders then. // This fails with 'The parameter is incorrect.' return d3d9Device.CreateTexture( width, height, 1, D3d9.Usage.RenderTarget, d3d9Format, D3d9.Pool.Default, ref sharedHandle ); } static D3d9.Format d3d9FormatFromDxgi( Dxgi.Format format ) { assert( format == Dxgi.Format.B8G8R8A8_UNorm ); return D3d9.Format.A8R8G8B8; } static (uint deviceWidth, uint deviceHeight) getDeviceSize( Wpf.FrameworkElement frameworkElement ) { Wpf.DpiScale dpiScale = WpfMedia.VisualTreeHelper.GetDpi( frameworkElement ); uint deviceWidth = (uint)(frameworkElement.ActualWidth * dpiScale.DpiScaleX); uint deviceHeight = (uint)(frameworkElement.ActualHeight * dpiScale.DpiScaleY); return (deviceWidth, deviceHeight); } static D2d.ID2D1RenderTarget createD2dRenderTarget( D3d11.ID3D11Texture2D d3d11Texture2d ) { Dxgi.IDXGISurface dxgiSurface = d3d11Texture2d.QueryInterface<Dxgi.IDXGISurface>(); DCommon.PixelFormat pixelFormat = new( d3d11Texture2d.Description.Format, DCommon.AlphaMode.Premultiplied ); D2d.RenderTargetProperties renderTargetProperties = new( D2d.RenderTargetType.Hardware, pixelFormat, 96.0f, 96.0f, D2d.RenderTargetUsage.None, D2d.FeatureLevel.Default ); return d2dFactory.CreateDxgiSurfaceRenderTarget( dxgiSurface, renderTargetProperties ); } static D3d11.ID3D11Texture2D createSharedD3d11Texture2d( Dxgi.Format dxgiFormat, uint deviceWidth, uint deviceHeight ) { D3d11.Texture2DDescription textureDescription; textureDescription.ArraySize = 1; textureDescription.BindFlags = D3d11.BindFlags.RenderTarget /* | D3d11.BindFlags.ShaderResource*/; //D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; textureDescription.CPUAccessFlags = D3d11.CpuAccessFlags.None; textureDescription.Format = dxgiFormat; textureDescription.Width = deviceWidth; textureDescription.Height = deviceHeight; textureDescription.MipLevels = 1; textureDescription.SampleDescription = Dxgi.SampleDescription.Default; textureDescription.Usage = D3d11.ResourceUsage.Default; textureDescription.MiscFlags = D3d11.ResourceOptionFlags.Shared; D3d11.ID3D11Texture2D d3d11Texture2d = d3d11Device.CreateTexture2D( textureDescription ); assert( d3d11Texture2d.QueryInterface<Dxgi.IDXGIResource>().SharedHandle != 0 ); return d3d11Texture2d; } void onCompositionTargetRendering( object? sender, Sys.EventArgs e ) { Vortice.Mathematics.Color color = new( colorValue += 0.005f ); TheD3dImage.Lock(); d2dRenderTarget!.BeginDraw(); d2dRenderTarget.Clear( color ); d2dRenderTarget.EndDraw().CheckError(); TheD3dImage.AddDirtyRect( new Wpf.Int32Rect( 0, 0, TheD3dImage.PixelWidth, TheD3dImage.PixelHeight ) ); TheD3dImage.Unlock(); } static D3d9.IDirect3DDevice9Ex createD3d9Device( nint deviceWindowHandle ) { D3d9.PresentParameters presentParameters = new(); presentParameters.Windowed = true; presentParameters.SwapEffect = D3d9.SwapEffect.Discard; presentParameters.DeviceWindowHandle = deviceWindowHandle; presentParameters.PresentationInterval = D3d9.PresentInterval.Immediate; D3d9.CreateFlags creationFlags = D3d9.CreateFlags.HardwareVertexProcessing /*| D3d9.CreateFlags.Multithreaded*/ | D3d9.CreateFlags.FpuPreserve; return d3d9.CreateDeviceEx( 0 /*D3DADAPTER_DEFAULT*/, D3d9.DeviceType.Hardware, deviceWindowHandle, creationFlags, presentParameters ); } static void assert( bool x ) { if( !x ) throw new Sys.Exception(); } }
错误原因及修正方案
核心错误点
- 共享句柄类型不兼容:Direct3D9仅支持
SharedNTHandle类型的共享资源,而你创建Direct3D11纹理时使用的是默认Shared标志,导致Direct3D9无法识别该共享句柄。 - 纹理用法参数不匹配:打开共享纹理时,Direct3D9不能指定
RenderTarget用法,因为纹理的用法已由创建方(Direct3D11)定义,强行指定会导致参数错误。 - 设备线程安全问题:Direct3D9设备未启用多线程标志,可能导致跨线程操作时的隐性错误。
具体修正步骤
修改Direct3D11共享纹理的创建参数
在createSharedD3d11Texture2d方法中,将MiscFlags改为SharedNTHandle:textureDescription.MiscFlags = D3d11.ResourceOptionFlags.SharedNTHandle;调整Direct3D9打开共享纹理的用法
在getSharedD3d9Texture方法中,将Usage改为None:return d3d9Device.CreateTexture( width, height, 1, D3d9.Usage.None, d3d9Format, D3d9.Pool.Default, ref sharedHandle );启用Direct3D9设备的多线程支持
在createD3d9Device方法中,添加Multithreaded标志:D3d9.CreateFlags creationFlags = D3d9.CreateFlags.HardwareVertexProcessing | D3d9.CreateFlags.Multithreaded | D3d9.CreateFlags.FpuPreserve;添加渲染同步屏障
在onCompositionTargetRendering方法中,确保Direct3D11命令执行完成后再通知D3DImage更新:d2dRenderTarget!.BeginDraw(); d2dRenderTarget.Clear( color ); d2dRenderTarget.EndDraw().CheckError(); // 确保Direct3D11渲染命令完成 d3d11Device.ImmediateContext.Flush();
内容的提问来源于stack exchange,提问作者Mike Nakis




