将Direct2D渲染重定向到WPF控件

时间:2015-01-10 18:15:44

标签: c# c++ wpf directx direct2d

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

// 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 ...)

4 个答案:

答案 0 :(得分:3)

您必须实现一个托管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
}

请遵循本教程:Walkthrough: Hosting a Win32 Control in WPF

答案 1 :(得分:3)

我会尝试尽可能地根据WPF 4.5 Unleashed第19章回答这个问题。如果你想查找它,你可以在子部分找到所有信息&#34;混合DirectX内容与WPF内容&#34;。

你的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;
    }
}

在呈现UI之前触发CompositionTarget.Rendering事件。您应该在那里呈现您的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部分绘制框架
  • 不要在Direct 3D设备上调用Present。 WPF根据您传递给d3dImage.SetBackBuffer()的表面呈现自己的后备缓冲区。
  • 应该处理事件IsFrontBufferAvailableChanged,因为有时前缓冲区可能变得不可用(例如,当用户进入锁定屏幕时)。您应该根据缓冲区可用性释放或获取资源。

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

答案 2 :(得分:0)

可能使用WINFORMS,但不能使用WPF。 这是一篇关于WPF如何使用HWND的文档:

  

WPF如何使用Hwnds

     

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

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

复制

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

答案 3 :(得分:-2)

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

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