12

I'm developing a drawing application in Visual C++ by means of Direct2D. I have a demo application where:

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

And when I receive the WM_PAINT message I draw my shapes.

Now I need to develop a WPF control (a kind of Panel) that represents my new render target (therefore it would replace the main window m_hwnd), so that I can create a new (C#) WPF project with the main window that has as children my custom panel, while the rendering part remains in the native C++/CLI DLL project.

How can I do that? What I have to set as my new render target?

I thought to use the handle of my WPF window:

IntPtr windowHandle = new WindowInteropHelper(MyMainWindow).Handle;

But I need to draw on my panel and not on my window.

Please note that I don't want to use any WPF classes for the rendering part (Shapes, DrawingVisuals...)

Nick
  • 10,309
  • 21
  • 97
  • 201
  • 2
    I would search for an example of how to host Direct2D in a WPF application . good question . – eran otzap Jan 10 '15 at 23:18
  • Do you have access to the DLL? Can you change its code? I believe the call to CreateWindow() is problematic – Domysee Apr 26 '15 at 14:15
  • WPF is very close to WIC, so I would instead use d2d1.CreateWicBitmapRenderTarget with an IWicBitmap of yours. This IWicBitmap can then be used in a class deriving from WPF's abstract BitmapSource (just like InteropBitmap but with a class of your own because WPF doesn't expose its WIC bitmaps...). This BitmapSource can now be a child of the Panel. If you have a sample app to work on, we could develop this further. – Simon Mourier Apr 26 '15 at 16:26
  • @Dominik yes I can change the code of my native c++ project. – Nick Apr 27 '15 at 11:18

4 Answers4

3

You have to implement a class that hosts your Win32 Window m_hwnd. This class inherits from HwndHost.

Moreover, you have to override the HwndHost.BuildWindowCore and HwndHost.DestroyWindowCore methods:

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
}

Please follow this tutorial: Walkthrough: Hosting a Win32 Control in WPF.

gliderkite
  • 8,828
  • 6
  • 44
  • 80
3

I'll try to answer the question as best I can based on WPF 4.5 Unleashed Chapter 19. If you want to look it up, you can find all information there in the sub-section "Mixing DirectX Content with WPF Content".

Your C++ DLL should have 3 exposed methods Initialize(), Cleanup() and Render(). The interesting methods are Initialize() and InitD3D(), which is called by 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;
}

Lets move on to the XAML code:

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

I've set it as background of a button here, using an ImageBrush. I believe adding it as background is a good way to display the DirectX content. However, you can use the image in any way you like.

To initialize the rendering acquire a handle to the current window and call the Initialize() method of the DLL with it:

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

The CompositionTarget.Rendering event is fired just before the UI is rendered. You should render your DirectX content in there:

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();
    }
}

That was basically it, I hope it helps. Now just a few important sidenotes:

  • Always lock your image, to avoid that WPF draws frames partially
  • Dont call Present on the Direct 3D device. WPF presents its own backbuffer, based on the surface you passed to d3dImage.SetBackBuffer().
  • The event IsFrontBufferAvailableChanged should be handled because sometimes the frontbuffer can become unavailable (for example when the user enters the lock screen). You should free or acquire the resources based on the buffer availability.

    private void d3dImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
         if (d3dImage.IsFrontBufferAvailable)
         {
             initialize();
         }
         else
         {
             // Cleanup:
             CompositionTarget.Rendering -= CompositionTarget_Rendering;
             DLL.Cleanup();
         }
     }
    
Domysee
  • 12,718
  • 10
  • 53
  • 84
0

It would be possible with WINFORMS probably, but not with WPF. Here is a documentation talking about how WPF uses HWNDs:

How WPF Uses Hwnds

To make the most of WPF "HWND interop", you need to understand how WPF uses HWNDs. For any HWND, you cannot mix WPF rendering with DirectX rendering or GDI / GDI+ rendering. This has a number of implications. Primarily, in order to mix these rendering models at all, you must create an interoperation solution, and use designated segments of interoperation for each rendering model that you choose to use. Also, the rendering behavior creates an "airspace" restriction for what your interoperation solution can accomplish. The "airspace" concept is explained in greater detail in the topic Technology Regions Overview. All WPF elements on the screen are ultimately backed by a HWND. When you create a WPF Window, WPF creates a top-level HWND, and uses an HwndSource to put the Window and its WPF content inside the HWND. The rest of your WPF content in the application shares that singular HWND. An exception is menus, combo box drop downs, and other pop-ups. These elements create their own top-level window, which is why a WPF menu can potentially go past the edge of the window HWND that contains it. When you use HwndHost to put an HWND inside WPF, WPF informs Win32 how to position the new child HWND relative to the WPF Window HWND. A related concept to HWND is transparency within and between each HWND. This is also discussed in the topic Technology Regions Overview.

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

I would recomend you to study a way to keep track of your render area and maybe create a "hideous" child window in front of it. Other research you can maybe do is to try and find/get WPF graphics buffer and inject your rendered scene directly into it using pointers and some advanced memory programming.

Felype
  • 3,087
  • 2
  • 25
  • 36
  • How would it be possible by using Windows Forms? – Nick Apr 20 '15 at 09:49
  • In Windows forms each control has a handle in the C/C++ win32 HWND format, you can get it by calling Control.Handle property, it returns a IntPtr, I believe you can cast it to HWND. Reference: https://msdn.microsoft.com/library/system.windows.forms.control.handle%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 – Felype Apr 21 '15 at 03:39
-3

https://github.com/SonyWWS/ATF/ might help

This is a level-editor with an direct2d view included

user528322
  • 109
  • 1
  • 3