You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

WPF中使用Direct2D遇参数错误:CreateTexture调用失败排查

问题:使用Vortice在WPF中通过D3DImage共享Direct3D11纹理到Direct3D9失败

我正在使用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();
    }
}

错误原因及修正方案

核心错误点

  1. 共享句柄类型不兼容:Direct3D9仅支持SharedNTHandle类型的共享资源,而你创建Direct3D11纹理时使用的是默认Shared标志,导致Direct3D9无法识别该共享句柄。
  2. 纹理用法参数不匹配:打开共享纹理时,Direct3D9不能指定RenderTarget用法,因为纹理的用法已由创建方(Direct3D11)定义,强行指定会导致参数错误。
  3. 设备线程安全问题:Direct3D9设备未启用多线程标志,可能导致跨线程操作时的隐性错误。

具体修正步骤

  1. 修改Direct3D11共享纹理的创建参数
    createSharedD3d11Texture2d方法中,将MiscFlags改为SharedNTHandle

    textureDescription.MiscFlags = D3d11.ResourceOptionFlags.SharedNTHandle;
    
  2. 调整Direct3D9打开共享纹理的用法
    getSharedD3d9Texture方法中,将Usage改为None

    return d3d9Device.CreateTexture( width, height, 1, D3d9.Usage.None, d3d9Format, D3d9.Pool.Default, ref sharedHandle );
    
  3. 启用Direct3D9设备的多线程支持
    createD3d9Device方法中,添加Multithreaded标志:

    D3d9.CreateFlags creationFlags = D3d9.CreateFlags.HardwareVertexProcessing | D3d9.CreateFlags.Multithreaded | D3d9.CreateFlags.FpuPreserve;
    
  4. 添加渲染同步屏障
    onCompositionTargetRendering方法中,确保Direct3D11命令执行完成后再通知D3DImage更新:

    d2dRenderTarget!.BeginDraw();
    d2dRenderTarget.Clear( color );
    d2dRenderTarget.EndDraw().CheckError();
    // 确保Direct3D11渲染命令完成
    d3d11Device.ImmediateContext.Flush();
    

内容的提问来源于stack exchange,提问作者Mike Nakis

火山引擎 最新活动