Подтвердить что ты не робот

Перенаправить Direct2D-рендеринг в элемент управления WPF

Я разрабатываю приложение рисования в 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), так что я могу создать новый (С#) проект WPF с основным окном, имеющим дочерние элементы моей пользовательской панели, а часть рендеринга остается в собственном проекте С++/CLI DLL.

Как я могу это сделать? Что я должен установить в качестве моей новой цели рендеринга?

Я думал использовать дескриптор моего окна WPF:

IntPtr windowHandle = new WindowInteropHelper(MyMainWindow).Handle;

Но мне нужно рисовать на панели, а не в моем окне.

Обратите внимание, что я не хочу использовать классы WPF для части рендеринга (Shapes, DrawingVisuals...)

4b9b3361

Ответ 1

Вам нужно реализовать класс, в котором находится ваше Win32 Window m_hwnd. Этот класс наследуется от HwndHost.

Кроме того, вы должны переопределить методы HwndHost.BuildWindowCore и HwndHost.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
}

Пожалуйста, следуйте этому руководству: Пошаговое руководство. Хостинг Win32-элемента управления в WPF.

Ответ 2

Я постараюсь ответить на вопрос, насколько это возможно, на основе WPF 4.5 Unleashed Chapter 19. Если вы хотите его найти, вы можете найти всю информацию там, в подразделе "Смешение содержимого DirectX с содержимым WPF",

В С++ 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. Однако вы можете использовать изображение так, как вам нравится.

Чтобы инициализировать рендеринг, вы получите дескриптор текущего окна и вызовите с ним метод Initialize() DLL:

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 запускается непосредственно перед визуализацией пользовательского интерфейса. Вы должны сделать свой контент 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 частично рисует кадры
  • Не нажимайте "Present" на устройстве Direct 3D. WPF представляет свой собственный backbuffer, основанный на поверхности, которую вы передали d3dImage.SetBackBuffer().
  • Событие IsFrontBufferAvailableChanged должно быть обработано, потому что иногда фронтбуфер может стать недоступным (например, когда пользователь входит в экран блокировки). Вы должны освобождать или приобретать ресурсы на основе доступности буфера.

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

Ответ 3

Возможно, с WINFORMS возможно, но не с WPF. Вот документация о том, как WPF использует HWND:

Как WPF использует Hwnds

Чтобы максимально использовать WPF "HWND interop", вам нужно понять, как WPF использует HWND. Для любого HWND вы не можете смешивать рендеринг WPF с DirectX рендеринга или GDI/GDI + рендеринга. Это имеет ряд последствий. Прежде всего, чтобы вообще смешать эти модели рендеринга, вы должны создать решение для взаимодействия и использовать определенные сегменты взаимодействие для каждой модели рендеринга, которую вы решите использовать. Также, поведение рендеринга создает ограничение "воздушного пространства" для того, решение взаимодействия может быть выполнено. Концепция "воздушного пространства" более подробно объясняется в разделе "Обзор технологических областей". Все элементы WPF на экране в конечном итоге поддерживаются HWND. когда вы создаете окно WPF, WPF создает HWND верхнего уровня и использует HwndSource для размещения окна и его содержимого WPF внутри HWND. остальная часть вашего содержимого WPF в приложении разделяет это единственное HWND. Исключением являются меню, выпадающие списки со списков и другие всплывающие окна. Эти элементы создают свое собственное окно верхнего уровня, поэтому меню WPF может потенциально пройти мимо края HWND окна, который содержит его. Когда вы используете HwndHost для размещения HWND внутри WPF, WPF сообщает Win32, как для размещения нового дочернего HWND относительно окна HWND WPF. связанная с HWND - это прозрачность внутри и между каждым HWND. Это также обсуждается в разделе Обзор технологических областей.

Скопировано из https://msdn.microsoft.com/en-us/library/ms742522%28v=vs.110%29.aspx

Я бы посоветовал вам изучить способ отслеживания области рендеринга и, возможно, создать "отвратительное" дочернее окно перед ним. Другие исследования, которые вы можете сделать, это попытаться найти/получить графический буфер WPF и вставить визуализированную сцену непосредственно в него с помощью указателей и некоторого расширенного программирования памяти.

Ответ 4

https://github.com/SonyWWS/ATF/ может помочь

Это редактор уровня с включенным представлением direct2d