如何从旧版 .NET Framework 的托管代码将 Direct2D 渲染器初始化为 GDI 上下文
How initialize Direct2D renderer into GDI context from managed code for older version of .NET Framework
给定 Windows 形式的遗留桌面应用程序,托管代码(C# 和 VB 项目)运行ning 在 .NET Framework 3.5 上(由于超出此问题范围的原因,无法迁移到较新的 .NET),
如何逐步将代码从 GDI+ 转换为 Direct2D?或者可能到 Direct3D?
另一个限制是生成的应用程序在 Windows7 上工作,但我们将迁移到 Windows8 或 Windows10,如果这是让它发挥作用的唯一方法。
(推动力是与 Graphics.FillPath 一起使用时 GDI+ 纹理处理中的错误和较小的纹理缩放因子;但无论如何我们最终还是想转移到 Direct2D 或 Direct3D。)
如果我们的目标是 .NET Framework 4.0+ 和 Windows8+,那么我们想要做的事情就很简单了,如此处所述:
Direct2D and GDI Interoperability Overview
不幸的是,尝试使这些指令适应我们较旧的目标规范 运行 遇到了一系列障碍。
第一步是使用一些托管包装器访问 Direct2D。
(不确定 Microsoft 和其他地方的 wrappers/code examples/tutorials 是否针对 Direct2D 1.0 或 1.1。)
我知道的选项:
A. 用于托管代码的 Microsoft DirectX 9.0 (MDX)(最后更新 2006):
我看到了关于这个长期不受支持的软件包的令人沮丧的评论,以及使用 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 window。
坚持调整此以呈现 "GDI context",如上文 "Interoperability Overview" link 中所述。
C++ 代码来自 "interoperability" link:
// 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
如何前进
第一个问题:上面 link 中描述的 D2D/GDI 互操作性是否适用于指定的目标平台(.NET 3.5,Windows 7)?
如果没有,那么我的尝试是不可能的。尽管如果 Windows 7 是问题所在,那么“Windows 10 上的 .NET 3.5”的解决方案将值得了解。
第二个问题假设互操作性是可能的,那么我面临着 SlimDX 的限制?或者我忽略了什么? 我不希望将 C++ 项目添加到此解决方案,但如果可以预编译自定义 C++ dll,然后使用 [除了 SlimDX dll],那将是一个(勉强)可以容忍的解决方案。
而不是 C++ 代码,手动编写托管包装器来访问需要的东西[但我在 SlimDX 中找不到]来初始化 D2D/GDI 互操作性?如何从上面的 link 转换 C++ 代码?
更新
在 SlimDX 中找到了需要的调用。详情见我的回答。
刚刚在 SlimDX 中发现 DeviceContextRenderTarget class:
' Equivalent to "ID2D1Factory::CreateDCRenderTarget".
Dim target2 As New DeviceContextRenderTarget(factory, targetProperties)
要完成初始化,需要绑定那个DC。
来自互操作性的 C++ link:
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 class:
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
给定 Windows 形式的遗留桌面应用程序,托管代码(C# 和 VB 项目)运行ning 在 .NET Framework 3.5 上(由于超出此问题范围的原因,无法迁移到较新的 .NET), 如何逐步将代码从 GDI+ 转换为 Direct2D?或者可能到 Direct3D?
另一个限制是生成的应用程序在 Windows7 上工作,但我们将迁移到 Windows8 或 Windows10,如果这是让它发挥作用的唯一方法。
(推动力是与 Graphics.FillPath 一起使用时 GDI+ 纹理处理中的错误和较小的纹理缩放因子;但无论如何我们最终还是想转移到 Direct2D 或 Direct3D。)
如果我们的目标是 .NET Framework 4.0+ 和 Windows8+,那么我们想要做的事情就很简单了,如此处所述:
Direct2D and GDI Interoperability Overview
不幸的是,尝试使这些指令适应我们较旧的目标规范 运行 遇到了一系列障碍。
第一步是使用一些托管包装器访问 Direct2D。
(不确定 Microsoft 和其他地方的 wrappers/code examples/tutorials 是否针对 Direct2D 1.0 或 1.1。)
我知道的选项:
A. 用于托管代码的 Microsoft DirectX 9.0 (MDX)(最后更新 2006):
我看到了关于这个长期不受支持的软件包的令人沮丧的评论,以及使用 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 window。
坚持调整此以呈现 "GDI context",如上文 "Interoperability Overview" link 中所述。
C++ 代码来自 "interoperability" link:
// 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
如何前进
第一个问题:上面 link 中描述的 D2D/GDI 互操作性是否适用于指定的目标平台(.NET 3.5,Windows 7)?
如果没有,那么我的尝试是不可能的。尽管如果 Windows 7 是问题所在,那么“Windows 10 上的 .NET 3.5”的解决方案将值得了解。
第二个问题假设互操作性是可能的,那么我面临着 SlimDX 的限制?或者我忽略了什么? 我不希望将 C++ 项目添加到此解决方案,但如果可以预编译自定义 C++ dll,然后使用 [除了 SlimDX dll],那将是一个(勉强)可以容忍的解决方案。
而不是 C++ 代码,手动编写托管包装器来访问需要的东西[但我在 SlimDX 中找不到]来初始化 D2D/GDI 互操作性?如何从上面的 link 转换 C++ 代码?
更新
在 SlimDX 中找到了需要的调用。详情见我的回答。
刚刚在 SlimDX 中发现 DeviceContextRenderTarget class:
' Equivalent to "ID2D1Factory::CreateDCRenderTarget".
Dim target2 As New DeviceContextRenderTarget(factory, targetProperties)
要完成初始化,需要绑定那个DC。
来自互操作性的 C++ link:
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 class:
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