Kết hợp Silk.NET để hiển thị nội dung D3D9 và OpenGL trong WPF

Thiết kế cơ bản cho điều khiển kết xuất mở rộng:

Định nghĩa lớp cơ sở FramebufferBase để quản lý kích thước và tài nguyên hình ảnh.

public abstract class FramebufferBase : IDisposable
{
    public abstract int Width { get; }
    public abstract int Height { get; }
    public abstract D3DImage D3DSurface { get; }
    public abstract void Dispose();
}

Xây dựng lớp điều khiển cơ sở GameBase.

public abstract class GameBase<TFrame> : Control where TFrame : FramebufferBase

Thêm sự kiện chuẩn bị, kết xuất và cập nhật.

public abstract event Action Initialize;
public abstract event Action<TimeSpan> RenderFrame;
public abstract event Action<object, TimeSpan> PostRender;

Khai báo phương thức trừu tượng cốt lõi.

protected abstract void SetupResources();
protected abstract void DrawContent(DrawingContext ctx);
protected abstract void Resize(SizeChangedInfo sizeInfo);

Xử lý thay đổi kích thước và kết xuất.

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    if (!DesignerProperties.GetIsInDesignMode(this))
    {
        Resize(sizeInfo);
    }
}

protected override void OnRender(DrawingContext ctx)
{
    if (DesignerProperties.GetIsInDesignMode(this))
    {
        DrawDesignPreview(ctx);
    }
    else if (Frame != null && Frame.D3DSurface.IsFrontBufferAvailable)
    {
        DrawContent(ctx);
        _timer.Restart();
    }
}

Khởi động vòng lặp kết xuất.

public void StartRendering()
{
    if (!DesignerProperties.GetIsInDesignMode(this))
    {
        IsVisibleChanged += (s, e) => 
        {
            if ((bool)e.NewValue) 
                CompositionTarget.Rendering += OnCompositionRendering;
            else 
                CompositionTarget.Rendering -= OnCompositionRendering;
        };

        Loaded += (s, e) => InvalidateVisual();
        SetupResources();
    }
}

private void OnCompositionRendering(object sender, EventArgs e)
{
    var args = (RenderingEventArgs)e;
    if (_prevRenderTime != args.RenderingTime)
    {
        InvalidateVisual();
        UpdateFrameRate(args.RenderingTime);
        _prevRenderTime = args.RenderingTime;
    }
}

Khai báo biến và thuộc tính.

public static readonly DependencyProperty FrameRateProperty = 
    DependencyProperty.Register(nameof(FPS), typeof(int), typeof(GameBase<TFrame>));

protected TFrame Frame { get; set; }
protected Stopwatch _timer = Stopwatch.StartNew();
private TimeSpan _prevRenderTime = TimeSpan.Zero;
public int FPS
{
    get => (int)GetValue(FrameRateProperty);
    set => SetValue(FrameRateProperty, value);
}

Triển khai D3D9:

Thiết lập ngữ cảnh kết xuất D3D9.

public unsafe class D3DContext
{
    public IDirect3DDevice9Ex* Device { get; }
    public Format SurfaceFormat { get; }

    public D3DContext()
    {
        IDirect3D9Ex* d3d9;
        D3D9.GetApi().Direct3DCreate9Ex(D3D9.SdkVersion, &d3d9);

        Displaymodeex mode = new((uint)sizeof(Displaymodeex));
        d3d9->GetAdapterDisplayModeEx(D3D9.AdapterDefault, ref mode, null);

        PresentParameters parameters = new()
        {
            Windowed = 1,
            SwapEffect = Swapeffect.Discard,
            BackBufferFormat = mode.Format,
            BackBufferWidth = 1,
            BackBufferHeight = 1
        };
        
        IDirect3DDevice9Ex* device;
        d3d9->CreateDeviceEx(/*...*/, &device);
        Device = device;
        SurfaceFormat = mode.Format;
    }
}

Tạo framebuffer cho D3D9.

public unsafe class D3DFrameBuffer : FramebufferBase
{
    public D3DContext Context { get; }
    public override int Width { get; }
    public override int Height { get; }
    public override D3DImage D3DSurface { get; }

    public D3DFrameBuffer(D3DContext ctx, int w, int h)
    {
        Context = ctx;
        Width = w;
        Height = h;

        IDirect3DSurface9* surface;
        ctx.Device->CreateRenderTarget(/*...*/, &surface);
        
        D3DSurface = new D3DImage();
        D3DSurface.Lock();
        D3DSurface.SetBackBuffer(D3DResourceType.IDirect3DSurface9, (IntPtr)surface);
        D3DSurface.Unlock();
    }
}

Điều khiển kết xuất D3D9.

public unsafe class D3DControl : GameBase<D3DFrameBuffer>
{
    private D3DContext _ctx;

    protected override void SetupResources()
    {
        _ctx = new D3DContext();
        Initialize?.Invoke();
    }

    protected override void Resize(SizeChangedInfo info)
    {
        if (_ctx != null && info.NewSize.Width > 0)
        {
            Frame?.Dispose();
            Frame = new D3DFrameBuffer(_ctx, (int)info.NewSize.Width, (int)info.NewSize.Height);
        }
    }

    protected override void DrawContent(DrawingContext ctx)
    {
        Frame.D3DSurface.Lock();
        RenderFrame?.Invoke(_timer.Elapsed);
        Frame.D3DSurface.AddDirtyRect(new Int32Rect(0, 0, Width, Height));
        Frame.D3DSurface.Unlock();

        ctx.DrawImage(Frame.D3DSurface, new Rect(0, 0, Width, Height));
        PostRender?.Invoke(this, _timer.Elapsed);
    }
}

Triển khai OpenGL:

Thiết lập ngữ cảnh chia sẻ OpenGL.

public unsafe class GLContext
{
    public IntPtr DxDeviceHandle { get; }
    public IntPtr GlContextHandle { get; }
    public IGraphicsContext GraphicsContext { get; }

    public GLContext(GLConfig config)
    {
        // Khởi tạo thiết bị D3D9
        IDirect3DDevice9Ex* d3dDevice = InitializeD3D();
        DxDeviceHandle = (IntPtr)d3dDevice;

        // Tạo ngữ cảnh OpenGL chia sẻ
        GraphicsContext = CreateSharedGLContext(config);
        GlContextHandle = Wgl.DXOpenDeviceNV(DxDeviceHandle);
    }
}

Framebuffer liên kết D3D-OpenGL.

public class GLFrameBuffer : FramebufferBase
{
    public int FboHandle { get; }
    public int ColorTexture { get; }

    public GLFrameBuffer(GLContext ctx, int w, int h)
    {
        // Tạo surface D3D9
        IDirect3DSurface9* surface = CreateD3DSurface(ctx, w, h);
        
        // Đăng ký tài nguyên chia sẻ
        IntPtr dxResource = Wgl.DXRegisterObjectNV(/*...*/);
        
        // Tạo FBO OpenGL
        FboHandle = GL.GenFramebuffer();
        GL.BindFramebuffer(FramebufferTarget.Framebuffer, FboHandle);
        GL.FramebufferTexture2D(/*...*/);
        
        // Liên kết với D3DImage
        D3DSurface = new D3DImage();
        D3DSurface.SetBackBuffer(D3DResourceType.IDirect3DSurface9, (IntPtr)surface);
    }
}

Điều khiển kết xuất OpenGL.

public class GLControl : GameBase<GLFrameBuffer>
{
    protected override void DrawContent(DrawingContext ctx)
    {
        Frame.D3DSurface.Lock();
        
        Wgl.DXLockObjectsNV(/*...*/);
        GL.BindFramebuffer(FramebufferTarget.Framebuffer, Frame.FboHandle);
        
        RenderFrame?.Invoke(_timer.Elapsed);
        
        GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
        Wgl.DXUnlockObjectsNV(/*...*/);
        
        // Áp dụng biến đổi tọa độ
        ctx.PushTransform(new TranslateTransform(0, Height));
        ctx.PushTransform(new ScaleTransform(1, -1));
        ctx.DrawImage(Frame.D3DSurface, new Rect(0, 0, Width, Height));
        ctx.Pop();
        ctx.Pop();
        
        PostRender?.Invoke(this, _timer.Elapsed);
    }
}

Thẻ: WPF Silk.NET Direct3D9 OpenGL interop

Đăng vào ngày 21 tháng 7 lúc 04:07