【问题标题】:How initialize Direct2D renderer into GDI context from managed code for older version of .NET Framework如何从旧版本 .NET Framework 的托管代码将 Direct2D 渲染器初始化为 GDI 上下文
【发布时间】:2017-09-16 22:49:43
【问题描述】:

鉴于 Windows 窗体 中的旧桌面应用程序,在 .NET Framework 3.5 上运行的托管代码(C# 和 VB 项目的混合) (由于超出此问题范围的原因,无法迁移到较新的 .NET), 如何逐步将代码从 GDI+ 转换为 Direct2D?或者可能是 Direct3D?

另一个限制是生成的应用程序可以在 Windows 7 上运行,但如果这是使其运行的唯一方法,我们将迁移到 Windows 8 或 Windows 10。

(推动力是 GDI+ 在与 Graphics.FillPath 和小的纹理缩放因子一起使用时处理纹理中的错误;但我们最终还是希望转向 Direct2D 或 Direct3D。)

如果我们以 .NET Framework 4.0+ 和 Windows 8+ 为目标,我们想做的事情将很简单,如下所述:
Direct2D and GDI Interoperability Overview

不幸的是,尝试使这些指令适应我们旧的目标规范遇到了一系列障碍。

第一步是使用一些托管包装器来访问 Direct2D。
(不确定是否 Direct2D 1.0 或 1.1 是 Microsoft 和其他地方的包装器/代码示例/教程的目标。)

我知道的选项:
A. Microsoft DirectX 9.0 for Managed Code (MDX)(最后更新 2006 年):
我看到 cmets 对这个长期不受支持的软件包感到沮丧,并建议改用 SlimDX 或 SharpDX [或迁移到受支持但与我们指定的旧平台不兼容的更新的 Microsoft 技术]。似乎不是一个好的长期方向。所以我还没有尝试过。
B. Win2D - 不支持 Windows 7,也不支持 .NET Framework 3.5。
C. SharpDX(开源,积极维护):
试过用这个。不幸的是,直到 v.3.0.0 才添加 Direct2D,它需要 .NET Framework 4.0+。所以这不是一个选择,直到我们准备好对我们的应用程序进行更重大的改革。

D. SlimDX(开源,2012 年最后更新): 成功安装并呈现到独立 Direct2D 窗口。
坚持调整此以呈现到“GDI 上下文”,如上面链接的“互操作性概述”中所述。

来自“互操作性”链接的 C++ 代码:

// Create a DC render target.
D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
    D2D1_RENDER_TARGET_TYPE_DEFAULT,
    D2D1::PixelFormat(
        DXGI_FORMAT_B8G8R8A8_UNORM,
        D2D1_ALPHA_MODE_IGNORE),
    0,
    0,
    D2D1_RENDER_TARGET_USAGE_NONE,
    D2D1_FEATURE_LEVEL_DEFAULT
    );

hr = m_pD2DFactory->CreateDCRenderTarget(&props, &m_pDCRT);

尝试编写VB代码:

Dim factory As New Direct2D.Factory

' --- THIS WORKS using SlimDX, SlimDX.Direct2D ---
'   (But it is not what I need; taken from SlimDX sample code)
' Stand-alone D2D window (NOT to GDI)
' "IntPtr handle" is "The window handle to associate with the device.".
Dim windowProperties As New WindowRenderTargetProperties(handle, New Size(600, 600))
Dim target As New WindowRenderTarget(factory, windowProperties)

' --- Hand-Translation of C++ code from "interoperability" link ---
Dim targetProperties As New RenderTargetProperties
targetProperties.Type = RenderTargetType.Default
targetProperties.PixelFormat = New PixelFormat(Format.B8G8R8A8_UNorm, AlphaMode.Ignore)
' *** How invoke "ID2D1Factory::CreateDCRenderTarget"? ***
'  (There aren't many methods on SlimDX.Direct2D.Factory "factory" above,
'   so if it is possible at all, SlimDX must do this some other way.)
' TODO

如何前进

第一个问题:上面链接中描述的 D2D/GDI 互操作性是否可用于指定的目标平台(.NET 3.5、Windows 7)?

如果不是,那么我正在尝试的事情是不可能的。虽然如果 Windows 7 是问题所在,那么“Windows 10 上的 .NET 3.5”的解决方案将是值得了解的。

第二个问题假设互操作性是可能的,那么我面临SlimDX的限制吗?或者我忽略了什么?我不想在这个解决方案中添加 C++ 项目,但是如果可以预编译自定义 C++ dll,然后使用 [除了 SlimDX dll],那将是一个(几乎)可以容忍的解决方案。

手动编写托管包装器而不是 C++ 代码来访问所需的 [但我在 SlimDX 中找不到] 来初始化 D2D/GDI 互操作性?如何从上面那个链接转换 C++ 代码?

更新

在 SlimDX 中找到所需的调用。详情见我的回答。

【问题讨论】:

  • 您无需包含整个 SlimDX 项目即可使用它。下载他们的 SDK 并添加对 .dll 文件的引用(它们以预编译的形式出现)。您可以在 Developer SDK 下的 their website 上找到它。
  • @VisualVincent - 是的,这就是我正在做的。我现在已经添加了相关的 VB 代码,所以你可以看到我卡在哪里了。
  • (我提到添加一些 DLL 是因为我需要的功能似乎不在 SlimDX 中。)
  • 哦,我刚刚明白你的意思:你不想写你自己的包装器。 - 如果事实证明不存在适合您需求的包装器,为什么为您需要的功能编写自己的包装器会如此糟糕?我一直混合使用 VB.NET 和 C++,因为它使(本机)代码更易于使用,并且也不太可能失败,因为您可以使包装器比原始函数简单得多。

标签: vb.net winforms windows-7 gdi+ direct2d


【解决方案1】:

刚刚在 SlimDX 中发现了 DeviceContextRenderTarget 类:

' Equivalent to "ID2D1Factory::CreateDCRenderTarget".
    Dim target2 As New DeviceContextRenderTarget(factory, targetProperties)

要完成初始化,需要绑定那个DC。

来自互操作性链接的 C++:

HRESULT DemoApp::OnRender(const PAINTSTRUCT &ps)
{

    HRESULT hr;
    RECT rc;

    // Get the dimensions of the client drawing area.
    GetClientRect(m_hwnd, &rc);

    // Create the DC render target.
    hr = CreateDeviceResources();

    if (SUCCEEDED(hr))
    {
        // Bind the DC to the DC render target.
        hr = m_pDCRT->BindDC(ps.hdc, &rc);


        // Draw with Direct2D.

        m_pDCRT->BeginDraw();

        m_pDCRT->SetTransform(D2D1::Matrix3x2F::Identity());

        m_pDCRT->Clear(D2D1::ColorF(D2D1::ColorF::White));

        m_pDCRT->DrawEllipse(
            D2D1::Ellipse(
                D2D1::Point2F(150.0f, 150.0f),
                100.0f,
                100.0f),
            m_pBlackBrush,
            3.0
            );

        hr = m_pDCRT->EndDraw();

        // Draw some GDI content.
        if (SUCCEEDED(hr))
        {
         ...
        }
    }

    if (hr == D2DERR_RECREATE_TARGET)
    {
        hr = S_OK;
        DiscardDeviceResources();
    }

    return hr;
}

VB翻译:

' "canvas" is the Windows control (tested with Panel) that I wish to draw D2D in.
Private Sub canvas_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles canvas.Paint
    ' Render GDI content that is below D2D content
    '... existing GDI calls ...

    ' Render Direct2D content.
    cDirect2DRenderer.TestRendering(e.Graphics, canvas.ClientSize)

    ' Render GDI content that is above D2D content.
    '... existing GDI calls ...
End Sub

使用VB类:

Imports System.Drawing

Imports SlimDX
Imports SlimDX.Direct2D
Imports SlimDX.DXGI

Public Class cDirect2DRenderer

#Region "=== Shared ==="
    Public Shared Sub TestRendering(gr As Graphics, canvasSize As System.Drawing.Size)
        Dim renderer As New cDirect2DRenderer

        ' CAUTION: After this, must call EndDraw or ReleaseHDC when done drawing.
        Dim success As Boolean = renderer.BeginDraw(gr, canvasSize)

        ' Render some Direct2D content.
        success = renderer.Test_Render(success)

        success = renderer.EndDraw(gr, success)
        If Not success Then
            'TODO: Log error.
        End If

        renderer.Dispose() : renderer = Nothing
    End Sub
#End Region


#Region "=== Fields, Constructor, Dispose ==="
    Private Ready As Boolean
    Private _factory As New Direct2D.Factory
    Private Target As DeviceContextRenderTarget
    Private Bounds As Rectangle
    Private Hdc As IntPtr

    Public Sub New()

    End Sub

    Public Sub Dispose()
        If Target IsNot Nothing Then
            Target.Dispose() : Target = Nothing
        End If

        Ready = False
    End Sub
#End Region


#Region "=== BeginDraw, Test_Render, EndDraw ==="
    Public Property Factory As Direct2D.Factory
        Get
            Return _factory
        End Get
        Set(value As Direct2D.Factory)
            If Exists(_factory) Then
                _factory.Dispose()
                '_factory = Nothing
            End If

            _factory = value
        End Set
    End Property

    ' True if Ready to draw.
    ' CAUTION: Even if returns False, Caller must call EndDraw, so that ReleaseHDC is called.
    Public Function BeginDraw(g As Graphics, canvasSize As System.Drawing.Size) As Boolean
        ' CAUTION: After this, must call EndDraw or ReleaseHDC when done drawing.
        EnsureReady(g, canvasSize)
        If Not Ready Then
            ' Initialization failed.
            Return False
        End If

        Try
            Dim success As Boolean = True
            Target.BeginDraw()

            Return success

        Catch ex As Exception
            Return False
        End Try
    End Function

    Public Function Test_Render(success As Boolean) As Boolean
        Try
            Target.Transform = Matrix3x2.Identity
            Target.Clear(New Color4(Color.BlueViolet))
            Dim brush As Direct2D.Brush = New SolidColorBrush(Target, New Color4(Color.Black))
            Dim ellipse As Direct2D.Ellipse = New Ellipse() With {
                    .Center = New PointF(100, 100),
                    .RadiusX = 80, .RadiusY = 80}
            Target.DrawEllipse(brush, ellipse)
            Target.FillEllipse(brush, ellipse)

        Catch ex As Exception
            success = False
        End Try

        Return success
    End Function

    ' True if rendering succeeds.
    ' "success" is accumulation, included in the return value.
    Public Function EndDraw(g As Graphics, success As Boolean) As Boolean
        ' Wrap EndDraw in Try, because "ReleaseHDC" must always be called.
        Try
            ' EndDraw is always called (even if "success" is already False).
            success = success And Target.EndDraw().IsSuccess
        Catch ex As Exception
            success = False
        End Try

        ReleaseHDC(g)
        ' TBD: This could be moved out elsewhere.
        EnsureFactoryReleased()

        If Not success Then
            Trouble()
        End If
        Return success
    End Function

    ' CAUTION: Caller must call EndDraw or ReleaseHDC when done drawing.
    Private Sub EnsureReady(g As Graphics, canvasSize As System.Drawing.Size)
        Dim newBounds As New Rectangle(0, 0, canvasSize.Width, canvasSize.Height)

        If Not Ready OrElse Not SameBounds(newBounds) Then
            If Ready Then
                Dispose()
            End If

            Me.Bounds = newBounds

            Me.Ready = InitializeDevice(g)
        End If
    End Sub

    ' AFTER set Me.Bounds.
    ' CAUTION: Caller must call g.ReleaseHdc(Me.Hdc) when done drawing.
    Private Function InitializeDevice(g As Graphics) As Boolean
        Try
            '' Stand-alone D2D window (NOT to GDI)
            ' ...width As Integer, height As Integer
            'Dim windowProperties As New WindowRenderTargetProperties(handle, New Size(600, 600))
            'Dim target1 As New WindowRenderTarget(factory, windowProperties)

            Dim targetProperties As New RenderTargetProperties
            targetProperties.Type = RenderTargetType.Default
            targetProperties.PixelFormat = New PixelFormat(Format.B8G8R8A8_UNorm, AlphaMode.Ignore)
            ' Equivalent to "ID2D1Factory::CreateDCRenderTarget".
            Me.Target = New DeviceContextRenderTarget(Me.Factory, targetProperties)

            ' CAUTION: Caller must call g.ReleaseHdc(Me.Hdc) when done drawing.
            Me.Hdc = g.GetHdc()
            Try
                'TestStr = Me.Hdc.ToString()
                Dim result As SlimDX.Result = Target.BindDeviceContext(Me.Hdc, Me.Bounds)

                If Not result.IsSuccess Then
                    ReleaseHDC(g)
                End If
                Return result.IsSuccess

            Catch ex As Exception
                ReleaseHDC(g)
                Return False
            End Try

        Catch ex As Exception
            Return False
        End Try
    End Function

    Private Sub ReleaseHDC(g As Graphics)
        Try
            g.ReleaseHdc(Me.Hdc)
        Finally
            Me.Hdc = Nothing
        End Try
    End Sub

    Private Sub EnsureFactoryReleased()
        Me.Factory = Nothing
    End Sub

    Private Function SameBounds(newBounds As Rectangle) As Boolean
        ' TBD: Does Equals do what we need?
        Return (newBounds.Equals(Me.Bounds))
    End Function
#End Region

End Class

【讨论】:

  • CreateDCRenderTarget 中的DC 代表DeciveContext,所以它应该正是您所需要的。 AFAIK SlimDX 是 DirectX 的完整包装,因此您可以在 C/C++ 中执行的任何操作都应该能够在 .NET 中执行,以及使用 SlimDX。
猜你喜欢
  • 2011-06-04
  • 2019-04-22
  • 1970-01-01
  • 1970-01-01
  • 2013-01-06
  • 1970-01-01
  • 2012-01-26
  • 1970-01-01
  • 2012-10-15
相关资源
最近更新 更多