将 Direct2D 呈现重定向到 WPF 控件

本文关键字:WPF 控件 重定向 Direct2D | 更新日期: 2023-09-27 18:37:06

我正在通过Direct2D开发Visual C++绘图应用程序。我有一个演示应用程序,其中:

// create the ID2D1Factory
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
// create the main window
HWND m_hwnd = CreateWindow(...);
// set the render target of type ID2D1HwndRenderTarget
m_pDirect2dFactory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(m_hwnd, size),
            &m_pRenderTarget
            );

当我收到WM_PAINT消息时,我会画出我的形状。

现在,我需要开发一个表示新呈现目标的 WPF 控件(一种 Panel)(因此它将替换主窗口m_hwnd),以便我可以创建一个新的 (C#) WPF 项目,其中主窗口将我的自定义面板作为子窗口,而呈现部分保留在本机 C++/CLI DLL 项目中。

我该怎么做?我必须将什么设置为新的呈现目标?

我想使用我的 WPF 窗口的句柄:

IntPtr windowHandle = new WindowInteropHelper(MyMainWindow).Handle;

但我需要在面板上画画,而不是在我的窗户上画画。

请注意,我不想对渲染部分使用任何 WPF 类(ShapesDrawingVisuals......

将 Direct2D 呈现重定向到 WPF 控件

您必须实现一个承载 Win32 窗口m_hwnd的类。此类继承自 HwndHost。

此外,您必须覆盖HwndHost.BuildWindowCoreHwndHost.DestroyWindowCore方法:

HandleRef BuildWindowCore(HandleRef hwndParent) 
{
  HWND parent = reinterpret_cast<HWND>(hwndParent.Handle.ToPointer());
  // here create your Window and set in the CreateWindow function
  // its parent by passing the parent variable defined above
  m_hwnd = CreateWindow(..., 
                        parent, 
                        ...);
  return HandleRef(this, IntPtr(m_hwnd));
}

void DestroyWindowCore(HandleRef hwnd) 
{
  DestroyWindow(m_hwnd);  // hwnd.Handle
}

请按照本教程操作:演练:在 WPF 中承载 Win32 控件。

我将尝试根据 WPF 4.5 释放的第 19 章尽我所能回答这个问题。如果要查找它,可以在"将 DirectX 内容与 WPF 内容混合"小节中找到所有信息。

您的C++ DLL 应该有 3 个公开的方法 Initialize()、Cleanup() 和 Render()。有趣的方法是 Initialize() 和 InitD3D(),它们由 Initialize() 调用:

extern "C" __declspec(dllexport) IDirect3DSurface9* WINAPI Initialize(HWND hwnd, int width, int height)
{
    // Initialize Direct3D
    if( SUCCEEDED( InitD3D( hwnd ) ) )
    {
        // Create the scene geometry
        if( SUCCEEDED( InitGeometry() ) )
        {
            if (FAILED(g_pd3dDevice->CreateRenderTarget(width, height, 
                D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, 
                true, // lockable (true for compatibility with Windows XP.  False is preferred for Windows Vista or later)
                &g_pd3dSurface, NULL)))
            {
                MessageBox(NULL, L"NULL!", L"Missing File", 0);
                return NULL;
            }
            g_pd3dDevice->SetRenderTarget(0, g_pd3dSurface);
        }
    }
    return g_pd3dSurface;
}

HRESULT InitD3D( HWND hWnd )
{
    // For Windows Vista or later, this would be better if it used Direct3DCreate9Ex:
    if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
        return E_FAIL;
    // Set up the structure used to create the D3DDevice. Since we are now
    // using more complex geometry, we will create a device with a zbuffer.
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory( &d3dpp, sizeof( d3dpp ) );
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
    // Create the D3DDevice
    if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                      &d3dpp, &g_pd3dDevice ) ) )
    {
        return E_FAIL;
    }
    // Turn on the zbuffer
    g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
    // Turn on ambient lighting 
    g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff );
    return S_OK;
}

让我们继续讨论 XAML 代码:

xmlns:interop="clr-namespace:System.Windows.Interop;assembly=PresentationCore"
<Button.Background>
    <ImageBrush>
        <ImageBrush.ImageSource>
            <interop:D3DImage x:Name="d3dImage" />
        </ImageBrush.ImageSource>
    </ImageBrush>
</Button.Background>

我在这里使用ImageBrush将其设置为按钮的背景。我相信将其添加为背景是显示 DirectX 内容的好方法。但是,您可以以任何您喜欢的方式使用图像。

要初始化渲染,请获取当前窗口的句柄,并使用它调用 DLL 的 Initialize() 方法:

private void initialize()
{
    IntPtr surface = DLL.Initialize(new WindowInteropHelper(this).Handle,
            (int)button.ActualWidth, (int)button.ActualHeight);
    if (surface != IntPtr.Zero)
    {
        d3dImage.Lock();
        d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface);
        d3dImage.Unlock();
        CompositionTarget.Rendering += CompositionTarget_Rendering;
    }
}

CompositionTarget.Rendering 事件在呈现 UI 之前触发。您应该在其中呈现 DirectX 内容:

private void CompositionTarget_Rendering(object sender, EventArgs e)
{
    if (d3dImage.IsFrontBufferAvailable)
    {
        d3dImage.Lock();
        DLL.Render();
        // Invalidate the whole area:
        d3dImage.AddDirtyRect(new Int32Rect(0, 0, d3dImage.PixelWidth, d3dImage.PixelHeight));
        d3dImage.Unlock();
    }
}

基本上就是这样,我希望它有所帮助。现在只是一些重要的旁注:

  • 始终锁定图像,以避免 WPF 部分绘制框架
  • 不要在直接 3D 设备上调用"演示"。WPF 根据传递给 d3dImage.SetBackBuffer() 的图面提供自己的后台缓冲区。
  • 应处理事件 IsFrontBufferAvailableChanged,因为有时前端缓冲区可能变得不可用(例如,当用户进入锁定屏幕时)。应根据缓冲区可用性释放或获取资源。

    private void d3dImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
         if (d3dImage.IsFrontBufferAvailable)
         {
             initialize();
         }
         else
         {
             // Cleanup:
             CompositionTarget.Rendering -= CompositionTarget_Rendering;
             DLL.Cleanup();
         }
     }
    
使用

WININFORMATION 可能是可能的,但不能使用 WPF。下面是一个讨论 WPF 如何使用 HWND 的文档:

WPF 如何使用 hwnds

若要充分利用 WPF"HWND 互操作",您需要了解 WPF 如何 使用 HWND。对于任何 HWND,您不能将 WPF 呈现与 DirectX 混合使用。 渲染或 GDI/GDI+ 渲染。这有许多含义。 首先,为了混合这些渲染模型,您必须 创建互操作解决方案,并使用指定的段 您选择使用的每个渲染模型的互操作。也 渲染行为会为您的 互操作解决方案可以完成。"空域"概念是 在技术区域概述主题中有更详细的说明。 屏幕上的所有 WPF 元素最终都由 HWND 提供支持。什么时候 创建一个 WPF 窗口,WPF 创建一个顶级 HWND,并使用 HwndSource 将窗口及其 WPF 内容放在 HWND 中。这 应用程序中的其余 WPF 内容共享该单数 HWND。 菜单、组合框下拉列表和其他弹出窗口除外。这些 元素创建自己的顶级窗口,这就是 WPF 菜单的原因 可能会越过包含它的窗口 HWND 的边缘。 当您使用 HwndHost 将 HWND 放入 WPF 中时,WPF 会通知 Win32 如何 以相对于 WPF 窗口 HWND 定位新的子 HWND。一个 与HWND相关的概念是每个HWND内部和之间的透明度。 技术区域概述主题中也对此进行了讨论。

复制自 https://msdn.microsoft.com/en-us/library/ms742522%28v=vs.110%29.aspx

我建议您研究一种方法来跟踪渲染区域,并可能在它前面创建一个"丑陋"的子窗口。您可以做的其他研究是尝试查找/获取 WPF 图形缓冲区,并使用指针和一些高级内存编程将渲染的场景直接注入其中。

https://github.com/SonyWWS/ATF/可能会有所帮助

这是一个包含 direct2d 视图的关卡编辑器