Мне удалось заставить его работать с помощью Vortice.Direct2D1.ID2D1Factory8.CreateHwndRenderTarget() , но это работает с HWND, поэтому у него проблемы с воздушным пространством.
Итак, теперь я пытаюсь как-то использовать System.Windows.Interop.D3DImage, у которого нет проблем с воздушным пространством.
Почему я все это делаю: Чтобы избежать ответов типа «о, вам следует сделать это совершенно по-другому», позвольте мне просто сказать, что я делаю это в качестве упражнения. Теперь, если вам интересно узнать настоящую причину, мне нужно создать элемент управления WPF, который отображает прокручиваемую диаграмму, потребляя при этом почти нулевую нагрузку на процессор. Для этого я не могу использовать встроенные средства рисования WPF, поскольку в WPF нет концепции прокрутки, поэтому в каждом кадре ему приходится перерисовывать все, что, конечно, потребляет немало ресурсов ЦП. Итак, мне нужно использовать некоторый механизм рендеринга, который позволяет прокручивать графическую поверхность и добавлять только ту графику, которая прокручивается. Direct2D является таким механизмом.
К сожалению, метод SetBackBuffer() D3DImage не принимает ничего, кроме IDirect3DSurface9, поэтому мне нужно как-то рисовать Direct2D на поверхности Direct3D9, но, похоже, нет простого способа добиться этого.
После долгих исследований и попыток разных вещей я обнаружил слухи и реальные признаки того, что этого можно достичь с помощью Direct3D11, DXGI и «дескрипторов общего доступа». Я нашел некоторый код и адаптировал его под свои нужды, попутно переписав его с C++ на C#, который должен работать следующим образом:
- Создайте «общий» Vortice.Direct3D11.ID3D11Texture2D. (Это работает в том смысле, что я получаю ненулевой общий дескриптор.)
- Интерфейс запроса преобразует ID3D11Texture2D в Vortice.DXGI.IDXGISurface. (Это работает.)
- Создайте Vortice.Direct2D1.ID2D1RenderTarget из IDXGISurface с помощью Vortice.Direct2D1.ID2D1Factory.CreateDxgiSurfaceRenderTarget(). (Это работает.)
- Откройте Vortice.Direct3D9.IDirect3DTexture9 из ID3D11Texture2D, используя Vortice.Direct3D9.IDirect3DDevice9Ex.CreateTexture() и передав ему ненулевой общий дескриптор. (Здесь у меня возникла проблема.)
- Установите IDirect3DTexture9 в качестве заднего буфера System.Windows.Interop.D3DImage.
- С этого момента продолжайте рисовать 2D-графику в ID2D1RenderTarget, которая фактически рисуется в ID3D11Texture2D, который рассматривается как IDirect3DTexture9 с помощью D3DImage.
Если я устанавливаю дескриптор общего ресурса равным нулю прямо перед вызовом функции, то это не дает сбоя, но, конечно, тогда я ничего не визуализирую.
Кто-нибудь может сказать мне, что я делаю? неправильно?
Вот мой исходный код, который очень близок к минимально воспроизводимому примеру:
Experiment.Direct3dImageEx.csproj:
net9.0-windows
13
WinExe
Debug;Release
true
App.ico
MainWindow.xaml:
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();
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().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();
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().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();
}
}