Skip to content

Commit 2c71826

Browse files
authoredDec 17, 2018
Merge pull request #1 from unknownv2/feature/direct3d9-overlay-engine
Add Direct3D9 and Direct3D11 Overlay Engines
2 parents 89dddef + f8789ec commit 2c71826

36 files changed

+2202
-125
lines changed
 

‎.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "deps/SharpDX.Desktop"]
2+
path = deps/SharpDX.Desktop
3+
url = git://github.com/unknownv2/SharpDX.Desktop.git

‎GraphicsHook.sln

+23-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2FDEAA0B-9
1111
EndProject
1212
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Direct3DCapture.Tests", "test\Direct3DCapture.Tests\Direct3DCapture.Tests.csproj", "{39FCDDD4-78AF-4F6F-BA40-724A5D1AE620}"
1313
EndProject
14-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DirectX.Direct3D9.Overlay", "src\DirectX.Direct3D9.Overlay\DirectX.Direct3D9.Overlay.csproj", "{A86B01F8-1FEA-485B-85D7-3941694EC0A4}"
14+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DirectX.Direct3D9.Overlay", "src\DirectX.Direct3D9.Overlay\DirectX.Direct3D9.Overlay.csproj", "{A86B01F8-1FEA-485B-85D7-3941694EC0A4}"
1515
EndProject
16-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DirectX.Direct3D11.Overlay", "src\DirectX.Direct3D11.Overlay\DirectX.Direct3D11.Overlay.csproj", "{96BC579E-FAFD-4152-8AFC-81905D7BF210}"
16+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DirectX.Direct3D11.Overlay", "src\DirectX.Direct3D11.Overlay\DirectX.Direct3D11.Overlay.csproj", "{96BC579E-FAFD-4152-8AFC-81905D7BF210}"
17+
EndProject
18+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DirectX.Direct3D.Core", "src\DirectX.Direct3D.Core\DirectX.Direct3D.Core.csproj", "{224F9343-46F6-4654-9381-E35239D179F1}"
19+
EndProject
20+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DirectX.Direct3D10.Overlay", "src\DirectX.Direct3D10.Overlay\DirectX.Direct3D10.Overlay.csproj", "{409B3409-61FE-4614-B755-843689E50EF6}"
21+
EndProject
22+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpDX.Desktop", "deps\SharpDX.Desktop\SharpDX.Desktop\SharpDX.Desktop.csproj", "{4062A245-60C4-415C-84A2-FA3E5D6B9D3F}"
1723
EndProject
1824
Global
1925
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -37,6 +43,18 @@ Global
3743
{96BC579E-FAFD-4152-8AFC-81905D7BF210}.Debug|Any CPU.Build.0 = Debug|Any CPU
3844
{96BC579E-FAFD-4152-8AFC-81905D7BF210}.Release|Any CPU.ActiveCfg = Release|Any CPU
3945
{96BC579E-FAFD-4152-8AFC-81905D7BF210}.Release|Any CPU.Build.0 = Release|Any CPU
46+
{224F9343-46F6-4654-9381-E35239D179F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47+
{224F9343-46F6-4654-9381-E35239D179F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
48+
{224F9343-46F6-4654-9381-E35239D179F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
49+
{224F9343-46F6-4654-9381-E35239D179F1}.Release|Any CPU.Build.0 = Release|Any CPU
50+
{409B3409-61FE-4614-B755-843689E50EF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51+
{409B3409-61FE-4614-B755-843689E50EF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
52+
{409B3409-61FE-4614-B755-843689E50EF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
53+
{409B3409-61FE-4614-B755-843689E50EF6}.Release|Any CPU.Build.0 = Release|Any CPU
54+
{4062A245-60C4-415C-84A2-FA3E5D6B9D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55+
{4062A245-60C4-415C-84A2-FA3E5D6B9D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
56+
{4062A245-60C4-415C-84A2-FA3E5D6B9D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
57+
{4062A245-60C4-415C-84A2-FA3E5D6B9D3F}.Release|Any CPU.Build.0 = Release|Any CPU
4058
EndGlobalSection
4159
GlobalSection(SolutionProperties) = preSolution
4260
HideSolutionNode = FALSE
@@ -46,6 +64,9 @@ Global
4664
{39FCDDD4-78AF-4F6F-BA40-724A5D1AE620} = {2FDEAA0B-9F04-4CB1-89CF-EF668584453A}
4765
{A86B01F8-1FEA-485B-85D7-3941694EC0A4} = {E9D61240-9E2C-4BD5-BEF1-607D3422EAAF}
4866
{96BC579E-FAFD-4152-8AFC-81905D7BF210} = {E9D61240-9E2C-4BD5-BEF1-607D3422EAAF}
67+
{224F9343-46F6-4654-9381-E35239D179F1} = {E9D61240-9E2C-4BD5-BEF1-607D3422EAAF}
68+
{409B3409-61FE-4614-B755-843689E50EF6} = {E9D61240-9E2C-4BD5-BEF1-607D3422EAAF}
69+
{4062A245-60C4-415C-84A2-FA3E5D6B9D3F} = {E9D61240-9E2C-4BD5-BEF1-607D3422EAAF}
4970
EndGlobalSection
5071
GlobalSection(ExtensibilityGlobals) = postSolution
5172
SolutionGuid = {15C4F934-433D-4655-9D2C-5E30E7A936C3}

‎README.md

+17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ Example tool for hooking graphics APIs using [CoreHook](https://github.com/unkno
55

66
Based on [Justin Stenning's Direct3DHook](https://github.com/spazzarama/Direct3DHook).
77

8+
## Requirements
9+
10+
**[Building the sample requires the .NET Core 3.0 SDK, which can be downloaded here.](https://dotnet.microsoft.com/download/dotnet-core/3.0)**
11+
12+
The Direct3D10 and Direct3D11 modules require the .NET Windows Form libraries, which are only available with .NET Core 3.0 and above.
13+
14+
The `deps` folder contains the [`SharpDX.Desktop`](https://github.com/unknownv2/SharpDX.Desktop) library targeting .NET Core 3.0, which is used by the Direct3D10 and Direct3D11 sample modules.
15+
16+
## Building
17+
18+
Clone and build the sample with:
19+
20+
```
21+
git clone --recursive git://github.com/unknownv2/graphics-hook.git
22+
cd graphics-hook
23+
dotnet build
24+
```
825

926
## References
1027

‎deps/SharpDX.Desktop

Submodule SharpDX.Desktop added at 18b895c

‎src/Direct3DCapture/Direct3DCapture.csproj

+16
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,20 @@
44
<TargetFramework>netstandard2.0</TargetFramework>
55
</PropertyGroup>
66

7+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9+
<OutputPath>$(OutputDir)</OutputPath>
10+
</PropertyGroup>
11+
12+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
13+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
14+
<OutputPath>$(OutputDir)</OutputPath>
15+
</PropertyGroup>
16+
17+
<ItemGroup>
18+
<PackageReference Include="CoreHook" Version="1.0.3" />
19+
<PackageReference Include="SharpDX.Direct3D9" Version="4.2.0" />
20+
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
21+
</ItemGroup>
22+
723
</Project>
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
using DirectX.Direct3D.Core.Drawing;
3+
using DirectX.Direct3D.Core.Memory;
4+
5+
namespace DirectX.Direct3D.Core
6+
{
7+
public abstract class Direct3DHook : DisposableComponent, IDirect3DHook
8+
{
9+
protected List<IOverlay> Overlays { get; set; }
10+
11+
protected bool PendingUpdate;
12+
13+
public abstract void CreateHooks();
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace DirectX.Direct3D.Core
4+
{
5+
public enum Direct3DVersion
6+
{
7+
Auto,
8+
Direct3D9,
9+
Direct3D10,
10+
Direct3D10_1,
11+
Direct3D11,
12+
Direct3D11_1,
13+
Direct3D12
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
8+
<OutputPath>$(OutputDir)</OutputPath>
9+
</PropertyGroup>
10+
11+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
12+
<OutputPath>$(OutputDir)</OutputPath>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="SharpDX" Version="4.2.0" />
17+
<PackageReference Include="System.Drawing.Common" Version="4.6.0-preview.18571.3" />
18+
</ItemGroup>
19+
20+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Drawing;
3+
4+
namespace DirectX.Direct3D.Core.Drawing
5+
{
6+
public class FramesPerSecondOverlay : TextOverlay
7+
{
8+
private string _fpsText = "{0:N0} FPS";
9+
10+
private int _frameCount;
11+
private int _lastTickCount;
12+
private float _lastFrameRate;
13+
public override string Text
14+
{
15+
get => string.Format(_fpsText, GetFramesPerSecond());
16+
set => _fpsText = value;
17+
}
18+
public FramesPerSecondOverlay(Font font) : base(font)
19+
{
20+
21+
}
22+
23+
public override void OnFrame()
24+
{
25+
_frameCount++;
26+
var tickCount = Environment.TickCount;
27+
if (Math.Abs(tickCount - _lastTickCount) > 1000)
28+
{
29+
_lastFrameRate = (float)_frameCount * 1000 / Math.Abs(tickCount - _lastTickCount);
30+
_frameCount = 0;
31+
_lastTickCount = tickCount;
32+
}
33+
}
34+
35+
public float GetFramesPerSecond()
36+
{
37+
return _lastFrameRate;
38+
}
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Collections.Generic;
2+
3+
namespace DirectX.Direct3D.Core.Drawing
4+
{
5+
public interface IOverlay : IOverlayElement
6+
{
7+
List<IOverlayElement> Elements { get; }
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace DirectX.Direct3D.Core.Drawing
4+
{
5+
public interface IOverlayElement : ICloneable
6+
{
7+
bool Hidden { get; }
8+
9+
void OnFrame();
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace DirectX.Direct3D.Core.Drawing
6+
{
7+
public class ImageOverlay : OverlayElement
8+
{
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace DirectX.Direct3D.Core.Drawing
5+
{
6+
[Serializable]
7+
public class Overlay : IOverlay
8+
{
9+
public List<IOverlayElement> Elements { get; } = new List<IOverlayElement>();
10+
11+
public bool Hidden { get; set; }
12+
13+
public object Clone() => MemberwiseClone();
14+
15+
public void OnFrame()
16+
{
17+
foreach (var overlayElement in Elements)
18+
{
19+
overlayElement.OnFrame();
20+
}
21+
}
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
3+
namespace DirectX.Direct3D.Core.Drawing
4+
{
5+
public class OverlayElement : IOverlayElement, IDisposable
6+
{
7+
public virtual bool Hidden { get; set; }
8+
9+
public object Clone() => MemberwiseClone();
10+
11+
public virtual void OnFrame()
12+
{
13+
}
14+
15+
protected virtual void Dispose(bool disposing)
16+
{
17+
}
18+
19+
public void Dispose()
20+
{
21+
Dispose(true);
22+
GC.SuppressFinalize(this);
23+
}
24+
25+
protected void SafeDispose(IDisposable disposableObject) => disposableObject?.Dispose();
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace DirectX.Direct3D.Core.Drawing
6+
{
7+
public class TextOverlay : OverlayElement
8+
{
9+
public virtual string Text { get; set; }
10+
11+
public virtual System.Drawing.Font Font { get; set; } = System.Drawing.SystemFonts.DefaultFont;
12+
13+
public virtual System.Drawing.Color Color { get; set; } = System.Drawing.Color.Black;
14+
15+
public virtual System.Drawing.Point Location { get; set; }
16+
17+
public virtual bool AntiAliased { get; set; } = false;
18+
19+
public TextOverlay()
20+
{
21+
}
22+
23+
public TextOverlay(System.Drawing.Font font)
24+
{
25+
Font = font;
26+
}
27+
}
28+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace DirectX.Direct3D.Core
6+
{
7+
public interface IDirect3DHook : IDisposable
8+
{
9+
void CreateHooks();
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace DirectX.Direct3D.Core.Memory
6+
{
7+
public abstract class DisposableBase : IDisposable
8+
{
9+
public bool IsDisposed { get; private set; }
10+
11+
public void Dispose()
12+
{
13+
CheckAndDispose(true);
14+
}
15+
16+
private void CheckAndDispose(bool disposing)
17+
{
18+
if (!IsDisposed)
19+
{
20+
IsDisposed = true;
21+
}
22+
}
23+
24+
protected abstract void Dispose(bool disposing);
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using SharpDX;
5+
6+
namespace DirectX.Direct3D.Core.Memory
7+
{
8+
public class DisposableComponent : DisposableBase
9+
{
10+
protected DisposeCollector DisposeCollector { get; set; }
11+
12+
protected override void Dispose(bool disposeManagedResources)
13+
{
14+
15+
}
16+
17+
protected internal T ToDispose<T>(T disposable)
18+
{
19+
if (!ReferenceEquals(disposable, null))
20+
{
21+
if (DisposeCollector == null)
22+
{
23+
DisposeCollector = new DisposeCollector();
24+
}
25+
26+
return DisposeCollector.Collect(disposable);
27+
}
28+
29+
return default(T);
30+
}
31+
32+
protected internal void RemoveAndDispose<T>(ref T disposable)
33+
{
34+
if (!ReferenceEquals(disposable, null))
35+
{
36+
DisposeCollector?.RemoveAndDispose(ref disposable);
37+
}
38+
}
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
3+
namespace DirectX.Direct3D.Core.Memory
4+
{
5+
public class DisposableEventArgs : EventArgs
6+
{
7+
public static readonly DisposableEventArgs DisposingEventArgs = new DisposableEventArgs(true);
8+
9+
public static readonly DisposableEventArgs NotDisposingEventArgs = new DisposableEventArgs(false);
10+
11+
public readonly bool Disposing;
12+
13+
private DisposableEventArgs(bool disposing)
14+
{
15+
Disposing = disposing;
16+
}
17+
18+
public static DisposableEventArgs Get(bool disposing)
19+
{
20+
return disposing ? DisposingEventArgs : NotDisposingEventArgs;
21+
}
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
8+
<OutputPath>$(OutputDir)</OutputPath>
9+
</PropertyGroup>
10+
11+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
12+
<OutputPath>$(OutputDir)</OutputPath>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="CoreHook" Version="1.0.3" />
17+
<PackageReference Include="Microsoft.WindowsDesktop.App" Version="3.0.0-alpha-27128-4" />
18+
<PackageReference Include="SharpDX.Direct3D10" Version="4.2.0" />
19+
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
20+
</ItemGroup>
21+
22+
</Project>
+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
using System;
2+
using SharpDX;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace DirectX.Direct3D11.Overlay
9+
{
10+
public abstract class Component : ComponentBase, IDisposable
11+
{
12+
/// <summary>
13+
/// Gets or sets the disposables.
14+
/// </summary>
15+
/// <value>The disposables.</value>
16+
protected DisposeCollector DisposeCollector { get; set; }
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="Component"/> class.
20+
/// </summary>
21+
protected internal Component()
22+
{
23+
}
24+
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="Component" /> class with an immutable name.
27+
/// </summary>
28+
/// <param name="name">The name.</param>
29+
protected Component(string name) : base(name)
30+
{
31+
}
32+
33+
/// <summary>
34+
/// Gets or sets a value indicating whether this instance is attached to a collector.
35+
/// </summary>
36+
/// <value>
37+
/// <c>true</c> if this instance is attached to a collector; otherwise, <c>false</c>.
38+
/// </value>
39+
internal bool IsAttached { get; set; }
40+
41+
/// <summary>
42+
/// Gets a value indicating whether this instance is disposed.
43+
/// </summary>
44+
/// <value>
45+
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
46+
/// </value>
47+
protected internal bool IsDisposed { get; private set; }
48+
49+
protected internal bool IsDisposing { get; private set; }
50+
51+
/// <summary>
52+
/// Occurs when when Dispose is called.
53+
/// </summary>
54+
public event EventHandler<EventArgs> Disposing;
55+
56+
/// <summary>
57+
/// Releases unmanaged and - optionally - managed resources
58+
/// </summary>
59+
public void Dispose()
60+
{
61+
if (!IsDisposed)
62+
{
63+
IsDisposing = true;
64+
65+
// Call the disposing event.
66+
var handler = Disposing;
67+
if (handler != null)
68+
{
69+
handler(this, EventArgs.Empty);
70+
}
71+
72+
Dispose(true);
73+
IsDisposed = true;
74+
}
75+
}
76+
77+
/// <summary>
78+
/// Disposes of object resources.
79+
/// </summary>
80+
/// <param name="disposeManagedResources">If true, managed resources should be
81+
/// disposed of in addition to unmanaged resources.</param>
82+
protected virtual void Dispose(bool disposeManagedResources)
83+
{
84+
if (disposeManagedResources)
85+
{
86+
// Dispose all ComObjects
87+
if (DisposeCollector != null)
88+
DisposeCollector.Dispose();
89+
DisposeCollector = null;
90+
}
91+
}
92+
93+
/// <summary>
94+
/// Adds a disposable object to the list of the objects to dispose.
95+
/// </summary>
96+
/// <param name="toDisposeArg">To dispose.</param>
97+
protected internal T ToDispose<T>(T toDisposeArg)
98+
{
99+
if (!ReferenceEquals(toDisposeArg, null))
100+
{
101+
if (DisposeCollector == null)
102+
DisposeCollector = new DisposeCollector();
103+
return DisposeCollector.Collect(toDisposeArg);
104+
}
105+
return default(T);
106+
}
107+
108+
/// <summary>
109+
/// Dispose a disposable object and set the reference to null. Removes this object from the ToDispose list.
110+
/// </summary>
111+
/// <param name="objectToDispose">Object to dispose.</param>
112+
protected internal void RemoveAndDispose<T>(ref T objectToDispose)
113+
{
114+
if (!ReferenceEquals(objectToDispose, null) && DisposeCollector != null)
115+
DisposeCollector.RemoveAndDispose(ref objectToDispose);
116+
}
117+
118+
/// <summary>
119+
/// Removes a disposable object to the list of the objects to dispose.
120+
/// </summary>
121+
/// <typeparam name="T"></typeparam>
122+
/// <param name="toDisposeArg">To dispose.</param>
123+
protected internal void RemoveToDispose<T>(T toDisposeArg)
124+
{
125+
if (!ReferenceEquals(toDisposeArg, null) && DisposeCollector != null)
126+
DisposeCollector.Remove(toDisposeArg);
127+
}
128+
}
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
namespace DirectX.Direct3D11.Overlay
8+
{
9+
public abstract class ComponentBase : IComponent, INotifyPropertyChanged
10+
{
11+
/// <summary>
12+
/// Occurs while this component is disposing and before it is disposed.
13+
/// </summary>
14+
//internal event EventHandler<EventArgs> Disposing;
15+
private string name;
16+
17+
/// <summary>
18+
/// Gets or sets a value indicating whether the name of this instance is immutable.
19+
/// </summary>
20+
/// <value><c>true</c> if this instance is name immutable; otherwise, <c>false</c>.</value>
21+
private readonly bool isNameImmutable;
22+
23+
private object tag;
24+
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="ComponentBase" /> class with a mutable name.
27+
/// </summary>
28+
protected ComponentBase()
29+
{
30+
}
31+
32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="ComponentBase" /> class with an immutable name.
34+
/// </summary>
35+
/// <param name="name">The name.</param>
36+
protected ComponentBase(string name)
37+
{
38+
if (name != null)
39+
{
40+
this.name = name;
41+
this.isNameImmutable = true;
42+
}
43+
}
44+
45+
/// <summary>
46+
/// Gets the name of this component.
47+
/// </summary>
48+
/// <value>The name.</value>
49+
[DefaultValue(null)]
50+
public string Name
51+
{
52+
get { return name; }
53+
set
54+
{
55+
if (isNameImmutable)
56+
throw new ArgumentException("Name property is immutable for this instance", "value");
57+
if (name == value) return;
58+
name = value;
59+
OnPropertyChanged("Name");
60+
}
61+
}
62+
63+
/// <summary>
64+
/// Gets or sets the tag associated to this object.
65+
/// </summary>
66+
/// <value>The tag.</value>
67+
#if !W8CORE
68+
[Browsable(false)]
69+
#endif
70+
[DefaultValue(null)]
71+
public object Tag
72+
{
73+
get
74+
{
75+
return tag;
76+
}
77+
set
78+
{
79+
if (ReferenceEquals(tag, value)) return;
80+
tag = value;
81+
OnPropertyChanged("Tag");
82+
}
83+
}
84+
85+
/// <summary>
86+
/// Occurs when a property value changes.
87+
/// </summary>
88+
public event PropertyChangedEventHandler PropertyChanged;
89+
90+
protected virtual void OnPropertyChanged(string propertyName)
91+
{
92+
PropertyChangedEventHandler handler = PropertyChanged;
93+
if (handler != null)
94+
{
95+
handler(this, new PropertyChangedEventArgs(propertyName));
96+
}
97+
}
98+
}
99+
}
+265
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using SharpDX.Direct3D11;
6+
using SharpDX;
7+
using System.Diagnostics;
8+
9+
namespace DirectX.Direct3D11.Overlay
10+
{
11+
public class DXFont : IDisposable
12+
{
13+
Device _device;
14+
DeviceContext _deviceContext;
15+
16+
public DXFont(Device device, DeviceContext deviceContext)
17+
{
18+
_device = device;
19+
_deviceContext = deviceContext;
20+
_initialized = false;
21+
_fontSheetTex = null;
22+
_fontSheetSRV = null;
23+
_texWidth = 1024;
24+
_texHeight = 0;
25+
_spaceWidth = 0;
26+
_charHeight = 0;
27+
}
28+
29+
public void Dispose()
30+
{
31+
if (_fontSheetTex != null)
32+
_fontSheetTex.Dispose();
33+
if (_fontSheetSRV != null)
34+
_fontSheetSRV.Dispose();
35+
36+
_fontSheetTex = null;
37+
_fontSheetSRV = null;
38+
_device = null;
39+
_deviceContext = null;
40+
}
41+
42+
enum STYLE
43+
{
44+
STYLE_NORMAL = 0,
45+
STYLE_BOLD = 1,
46+
STYLE_ITALIC = 2,
47+
STYLE_BOLD_ITALIC = 3,
48+
STYLE_UNDERLINE = 4,
49+
STYLE_STRIKEOUT = 8
50+
};
51+
52+
bool _initialized;
53+
const char StartChar = (char)33;
54+
const char EndChar = (char)127;
55+
const uint NumChars = EndChar - StartChar;
56+
ShaderResourceView _fontSheetSRV;
57+
Texture2D _fontSheetTex;
58+
int _texWidth, _texHeight;
59+
Rectangle[] _charRects = new Rectangle[NumChars];
60+
int _spaceWidth, _charHeight;
61+
62+
public bool Initialize(string FontName, float FontSize, System.Drawing.FontStyle FontStyle, bool AntiAliased)
63+
{
64+
Debug.Assert(!_initialized);
65+
System.Drawing.Font font = new System.Drawing.Font(FontName, FontSize, FontStyle, System.Drawing.GraphicsUnit.Pixel);
66+
67+
System.Drawing.Text.TextRenderingHint hint = AntiAliased ? System.Drawing.Text.TextRenderingHint.AntiAlias : System.Drawing.Text.TextRenderingHint.SystemDefault;
68+
69+
int tempSize = (int)(FontSize * 2);
70+
using (System.Drawing.Bitmap charBitmap = new System.Drawing.Bitmap(tempSize, tempSize, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
71+
{
72+
using (System.Drawing.Graphics charGraphics = System.Drawing.Graphics.FromImage(charBitmap))
73+
{
74+
charGraphics.PageUnit = System.Drawing.GraphicsUnit.Pixel;
75+
charGraphics.TextRenderingHint = hint;
76+
77+
MeasureChars(font, charGraphics);
78+
79+
using (var fontSheetBitmap = new System.Drawing.Bitmap(_texWidth, _texHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
80+
{
81+
using (var fontSheetGraphics = System.Drawing.Graphics.FromImage(fontSheetBitmap))
82+
{
83+
fontSheetGraphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
84+
fontSheetGraphics.Clear(System.Drawing.Color.FromArgb(0, System.Drawing.Color.Black));
85+
86+
BuildFontSheetBitmap(font, charGraphics, charBitmap, fontSheetGraphics);
87+
88+
if (!BuildFontSheetTexture(fontSheetBitmap))
89+
{
90+
return false;
91+
}
92+
}
93+
}
94+
}
95+
}
96+
97+
_initialized = true;
98+
99+
return true;
100+
}
101+
102+
private bool BuildFontSheetTexture(System.Drawing.Bitmap fontSheetBitmap)
103+
{
104+
System.Drawing.Imaging.BitmapData bmData;
105+
106+
bmData = fontSheetBitmap.LockBits(new System.Drawing.Rectangle(0, 0, _texWidth, _texHeight), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
107+
Texture2DDescription texDesc = new Texture2DDescription();
108+
texDesc.Width = _texWidth;
109+
texDesc.Height = _texHeight;
110+
texDesc.MipLevels = 1;
111+
texDesc.ArraySize = 1;
112+
texDesc.Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm;
113+
texDesc.SampleDescription.Count = 1;
114+
texDesc.SampleDescription.Quality = 0;
115+
texDesc.Usage = ResourceUsage.Immutable;
116+
texDesc.BindFlags = BindFlags.ShaderResource;
117+
texDesc.CpuAccessFlags = CpuAccessFlags.None;
118+
texDesc.OptionFlags = ResourceOptionFlags.None;
119+
120+
121+
SharpDX.DataBox data;
122+
data.DataPointer = bmData.Scan0;
123+
data.RowPitch = _texWidth * 4;
124+
data.SlicePitch = 0;
125+
126+
_fontSheetTex = new Texture2D(_device, texDesc, new[] { data });
127+
if (_fontSheetTex == null)
128+
return false;
129+
130+
ShaderResourceViewDescription srvDesc = new ShaderResourceViewDescription();
131+
srvDesc.Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm;
132+
srvDesc.Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.Texture2D;
133+
srvDesc.Texture2D.MipLevels = 1;
134+
srvDesc.Texture2D.MostDetailedMip = 0;
135+
136+
_fontSheetSRV = new ShaderResourceView(_device, _fontSheetTex, srvDesc);
137+
if (_fontSheetSRV == null)
138+
return false;
139+
140+
fontSheetBitmap.UnlockBits(bmData);
141+
142+
return true;
143+
}
144+
145+
void MeasureChars(System.Drawing.Font font, System.Drawing.Graphics charGraphics)
146+
{
147+
char[] allChars = new char[NumChars];
148+
149+
for (char i = (char)0; i < NumChars; ++i)
150+
allChars[i] = (char)(StartChar + i);
151+
152+
System.Drawing.SizeF size;
153+
size = charGraphics.MeasureString(new String(allChars), font, new System.Drawing.PointF(0, 0), System.Drawing.StringFormat.GenericDefault);
154+
155+
_charHeight = (int)(size.Height + 0.5f);
156+
157+
int numRows = (int)(size.Width / _texWidth) + 1;
158+
_texHeight = (numRows * _charHeight) + 1;
159+
160+
System.Drawing.StringFormat sf = System.Drawing.StringFormat.GenericDefault;
161+
sf.FormatFlags |= System.Drawing.StringFormatFlags.MeasureTrailingSpaces;
162+
size = charGraphics.MeasureString(" ", font, 0, sf);
163+
_spaceWidth = (int)(size.Width + 0.5f);
164+
}
165+
166+
void BuildFontSheetBitmap(System.Drawing.Font font, System.Drawing.Graphics charGraphics, System.Drawing.Bitmap charBitmap, System.Drawing.Graphics fontSheetGraphics)
167+
{
168+
System.Drawing.Brush whiteBrush = System.Drawing.Brushes.White;
169+
int fontSheetX = 0;
170+
int fontSheetY = 0;
171+
172+
173+
for (int i = 0; i < NumChars; ++i)
174+
{
175+
charGraphics.Clear(System.Drawing.Color.FromArgb(0, System.Drawing.Color.Black));
176+
charGraphics.DrawString(((char)(StartChar + i)).ToString(), font, whiteBrush, new System.Drawing.PointF(0.0f, 0.0f));
177+
178+
int minX = GetCharMinX(charBitmap);
179+
int maxX = GetCharMaxX(charBitmap);
180+
int charWidth = maxX - minX + 1;
181+
182+
if (fontSheetX + charWidth >= _texWidth)
183+
{
184+
fontSheetX = 0;
185+
fontSheetY += (int)(_charHeight) + 1;
186+
}
187+
188+
_charRects[i] = new Rectangle(fontSheetX, fontSheetY, charWidth, _charHeight);
189+
190+
fontSheetGraphics.DrawImage(charBitmap, fontSheetX, fontSheetY, new System.Drawing.Rectangle(minX, 0, charWidth, _charHeight), System.Drawing.GraphicsUnit.Pixel);
191+
192+
fontSheetX += charWidth + 1;
193+
}
194+
}
195+
196+
private int GetCharMaxX(System.Drawing.Bitmap charBitmap)
197+
{
198+
int width = charBitmap.Width;
199+
int height = charBitmap.Height;
200+
201+
for (int x = width - 1; x >= 0; --x)
202+
{
203+
for (int y = 0; y < height; ++y)
204+
{
205+
System.Drawing.Color color;
206+
207+
color = charBitmap.GetPixel(x, y);
208+
if (color.A > 0)
209+
return x;
210+
}
211+
}
212+
213+
return width - 1;
214+
}
215+
216+
private int GetCharMinX(System.Drawing.Bitmap charBitmap)
217+
{
218+
int width = charBitmap.Width;
219+
int height = charBitmap.Height;
220+
221+
for (int x = 0; x < width; ++x)
222+
{
223+
for (int y = 0; y < height; ++y)
224+
{
225+
System.Drawing.Color color;
226+
227+
color = charBitmap.GetPixel(x, y);
228+
if (color.A > 0)
229+
return x;
230+
}
231+
}
232+
233+
return 0;
234+
}
235+
236+
public ShaderResourceView GetFontSheetSRV()
237+
{
238+
Debug.Assert(_initialized);
239+
240+
return _fontSheetSRV;
241+
}
242+
243+
public Rectangle GetCharRect(char c)
244+
{
245+
Debug.Assert(_initialized);
246+
247+
return _charRects[c - StartChar];
248+
}
249+
250+
public int GetSpaceWidth()
251+
{
252+
Debug.Assert(_initialized);
253+
254+
return _spaceWidth;
255+
}
256+
257+
public int GetCharHeight()
258+
{
259+
Debug.Assert(_initialized);
260+
261+
return _charHeight;
262+
}
263+
264+
}
265+
}
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using SharpDX.Direct3D11;
6+
using SharpDX;
7+
using System.Diagnostics;
8+
9+
namespace DirectX.Direct3D11.Overlay
10+
{
11+
public class DXImage : Component
12+
{
13+
Device _device;
14+
DeviceContext _deviceContext;
15+
Texture2D _tex;
16+
ShaderResourceView _texSRV;
17+
int _texWidth, _texHeight;
18+
bool _initialised = false;
19+
20+
public int Width
21+
{
22+
get
23+
{
24+
return _texWidth;
25+
}
26+
}
27+
28+
public int Height
29+
{
30+
get
31+
{
32+
return _texHeight;
33+
}
34+
}
35+
36+
public Device Device
37+
{
38+
get { return _device; }
39+
}
40+
41+
public DXImage(Device device, DeviceContext deviceContext) : base("DXImage")
42+
{
43+
_device = device;
44+
_deviceContext = deviceContext;
45+
_tex = null;
46+
_texSRV = null;
47+
_texWidth = 0;
48+
_texHeight = 0;
49+
}
50+
51+
public bool Initialise(System.Drawing.Bitmap bitmap)
52+
{
53+
RemoveAndDispose(ref _tex);
54+
RemoveAndDispose(ref _texSRV);
55+
56+
//Debug.Assert(bitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb);
57+
System.Drawing.Imaging.BitmapData bmData;
58+
59+
_texWidth = bitmap.Width;
60+
_texHeight = bitmap.Height;
61+
62+
bmData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, _texWidth, _texHeight), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
63+
try
64+
{
65+
Texture2DDescription texDesc = new Texture2DDescription();
66+
texDesc.Width = _texWidth;
67+
texDesc.Height = _texHeight;
68+
texDesc.MipLevels = 1;
69+
texDesc.ArraySize = 1;
70+
texDesc.Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm;
71+
texDesc.SampleDescription.Count = 1;
72+
texDesc.SampleDescription.Quality = 0;
73+
texDesc.Usage = ResourceUsage.Immutable;
74+
texDesc.BindFlags = BindFlags.ShaderResource;
75+
texDesc.CpuAccessFlags = CpuAccessFlags.None;
76+
texDesc.OptionFlags = ResourceOptionFlags.None;
77+
78+
SharpDX.DataBox data;
79+
data.DataPointer = bmData.Scan0;
80+
data.RowPitch = bmData.Stride;// _texWidth * 4;
81+
data.SlicePitch = 0;
82+
83+
_tex = ToDispose(new Texture2D(_device, texDesc, new[] { data }));
84+
if (_tex == null)
85+
return false;
86+
87+
ShaderResourceViewDescription srvDesc = new ShaderResourceViewDescription();
88+
srvDesc.Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm;
89+
srvDesc.Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.Texture2D;
90+
srvDesc.Texture2D.MipLevels = 1;
91+
srvDesc.Texture2D.MostDetailedMip = 0;
92+
93+
_texSRV = ToDispose(new ShaderResourceView(_device, _tex, srvDesc));
94+
if (_texSRV == null)
95+
return false;
96+
}
97+
finally
98+
{
99+
bitmap.UnlockBits(bmData);
100+
}
101+
102+
_initialised = true;
103+
104+
return true;
105+
}
106+
107+
public ShaderResourceView GetSRV()
108+
{
109+
Debug.Assert(_initialised);
110+
return _texSRV;
111+
}
112+
}
113+
}

‎src/DirectX.Direct3D11.Overlay/DXSprite.cs

+423
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Runtime.InteropServices;
4+
using System.Drawing;
5+
using CoreHook;
6+
using DirectX.Direct3D.Core.Drawing;
7+
using DirectX.Direct3D.Core;
8+
using SharpDX.Direct3D;
9+
using SharpDX.Direct3D11;
10+
using SharpDX.DXGI;
11+
using Device = SharpDX.Direct3D11.Device;
12+
13+
namespace DirectX.Direct3D11.Overlay
14+
{
15+
internal class Direct3DHookModule : Direct3DHook
16+
{
17+
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
18+
delegate int DXGISwapChain_PresentDelegate(IntPtr swapChain, int syncInterval, PresentFlags flags);
19+
20+
private IHook<DXGISwapChain_PresentDelegate> _d3DPresentHook;
21+
private List<IntPtr> _d3DDeviceFunctions = new List<IntPtr>();
22+
private OverlayRenderer _overlayRenderer;
23+
24+
public const int DXGI_SWAPCHAIN_METHOD_COUNT = 18;
25+
Device _device;
26+
SwapChain _swapChain;
27+
28+
private IntPtr _swapChainPtr;
29+
30+
private static SwapChainDescription CreateSwapChainDescription(IntPtr windowHandle)
31+
{
32+
return new SwapChainDescription
33+
{
34+
BufferCount = 1,
35+
Flags = SwapChainFlags.None,
36+
IsWindowed = true,
37+
ModeDescription = new ModeDescription(100, 100, new Rational(60, 1), Format.R8G8B8A8_UNorm),
38+
OutputHandle = windowHandle,
39+
SampleDescription = new SampleDescription(1, 0),
40+
SwapEffect = SwapEffect.Discard,
41+
Usage = Usage.RenderTargetOutput
42+
};
43+
}
44+
45+
public override void CreateHooks()
46+
{
47+
var renderForm = new SharpDX.Windows.RenderForm();
48+
Device.CreateWithSwapChain(
49+
DriverType.Hardware,
50+
DeviceCreationFlags.BgraSupport,
51+
CreateSwapChainDescription(renderForm.Handle),
52+
out _device,
53+
out _swapChain);
54+
55+
if (_swapChain != null)
56+
{
57+
_d3DDeviceFunctions.AddRange(ReadVTableAddresses(_swapChain.NativePointer, DXGI_SWAPCHAIN_METHOD_COUNT));
58+
}
59+
60+
_d3DPresentHook = HookFactory.CreateHook<DXGISwapChain_PresentDelegate>(
61+
_d3DDeviceFunctions[(int)FunctionOrdinals.Present],
62+
Detour_Present,
63+
this);
64+
65+
Overlays = new List<IOverlay>
66+
{
67+
// Add the Frames Per Second overlay
68+
new Direct3D.Core.Drawing.Overlay
69+
{
70+
Elements =
71+
{
72+
new FramesPerSecondOverlay(new Font("Arial", 16, FontStyle.Bold))
73+
{
74+
Location = new Point(25, 25),
75+
Color = Color.Red,
76+
AntiAliased = true,
77+
Text = "{0:N0} FPS"
78+
}
79+
},
80+
Hidden = false
81+
}
82+
};
83+
84+
_d3DPresentHook.Enabled = true;
85+
}
86+
87+
private static IEnumerable<IntPtr> ReadVTableAddresses(IntPtr vTableAddress, int vTableFunctionCount)
88+
{
89+
IntPtr[] addresses = new IntPtr[vTableFunctionCount];
90+
IntPtr vTable = Marshal.ReadIntPtr(vTableAddress);
91+
for (var i = 0; i < vTableFunctionCount; ++i)
92+
{
93+
addresses[i] = Marshal.ReadIntPtr(vTable, i * IntPtr.Size);
94+
}
95+
return addresses;
96+
}
97+
98+
private int Detour_Present(IntPtr swapChainPtr, int syncInterval, SharpDX.DXGI.PresentFlags flags)
99+
{
100+
SwapChain swapChain = (SwapChain)swapChainPtr;
101+
102+
DrawFramesPerSecond(swapChain);
103+
104+
return _d3DPresentHook.Original(swapChainPtr, syncInterval, flags);
105+
}
106+
107+
private void DrawFramesPerSecond(SwapChain swapChain)
108+
{
109+
Capture(swapChain);
110+
}
111+
112+
private void Capture(SwapChain swapChain)
113+
{
114+
115+
try
116+
{
117+
// Draw any overlays
118+
var displayOverlays = Overlays;
119+
120+
if (_overlayRenderer == null ||
121+
_swapChainPtr != swapChain.NativePointer ||
122+
PendingUpdate)
123+
{
124+
if (_overlayRenderer != null)
125+
{
126+
_overlayRenderer.Dispose();
127+
}
128+
129+
_swapChainPtr = swapChain.NativePointer;
130+
131+
_overlayRenderer = ToDispose((new OverlayRenderer()));
132+
_overlayRenderer.Overlays.AddRange(displayOverlays);
133+
_overlayRenderer.Initialize(swapChain);
134+
PendingUpdate = false;
135+
}
136+
137+
if (_overlayRenderer != null)
138+
{
139+
foreach (var overlay in _overlayRenderer.Overlays)
140+
{
141+
overlay.OnFrame();
142+
}
143+
144+
_overlayRenderer.DrawFrame();
145+
}
146+
}
147+
catch (Exception e)
148+
{
149+
System.Diagnostics.Debug.WriteLine(e.ToString());
150+
}
151+
}
152+
}
153+
}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,36 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
4+
<TargetFrameworks>netcoreapp3.0</TargetFrameworks>
5+
</PropertyGroup>
6+
7+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9+
<OutputPath>$(OutputDir)</OutputPath>
10+
</PropertyGroup>
11+
12+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
13+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
14+
<OutputPath>$(OutputDir)</OutputPath>
15+
</PropertyGroup>
16+
17+
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netcoreapp3.0|AnyCPU'">
18+
<Optimize>false</Optimize>
519
</PropertyGroup>
620

721
<ItemGroup>
822
<PackageReference Include="CoreHook" Version="1.0.3" />
23+
<PackageReference Include="Microsoft.WindowsDesktop.App" Version="3.0.0-alpha-27128-4" />
24+
<PackageReference Include="SharpDX.D3DCompiler" Version="4.2.0" />
925
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
26+
<PackageReference Include="SharpDX.Direct3D11.Effects" Version="4.2.0" />
1027
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
28+
<PackageReference Include="System.Drawing.Common" Version="4.6.0-preview.18571.3" />
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<ProjectReference Include="..\..\deps\SharpDX.Desktop\SharpDX.Desktop\SharpDX.Desktop.csproj" />
33+
<ProjectReference Include="..\DirectX.Direct3D.Core\DirectX.Direct3D.Core.csproj" />
1134
</ItemGroup>
1235

1336
</Project>
+14-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
using System;
2-
using CoreHook;
3-
using SharpDX;
4-
using SharpDX.Direct3D11;
1+
using CoreHook;
2+
using DirectX.Direct3D.Core;
53

64
namespace DirectX.Direct3D11.Overlay
75
{
86
public class EntryPoint : IEntryPoint
97
{
8+
private Direct3DHook _direct3DHook;
9+
1010
public EntryPoint(IContext context) { }
1111

1212
public void Run(IContext context)
1313
{
14+
InitializeDeviceHook();
15+
while (true)
16+
{
17+
System.Threading.Thread.Sleep(30000);
18+
}
19+
}
1420

21+
private void InitializeDeviceHook()
22+
{
23+
_direct3DHook = new Direct3DHookModule();
24+
_direct3DHook.CreateHooks();
1525
}
1626
}
1727
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace DirectX.Direct3D11.Overlay
6+
{
7+
public enum FunctionOrdinals : short
8+
{
9+
// IUnknown
10+
QueryInterface = 0,
11+
AddRef = 1,
12+
Release = 2,
13+
14+
// IDXGIObject
15+
SetPrivateData = 3,
16+
SetPrivateDataInterface = 4,
17+
GetPrivateData = 5,
18+
GetParent = 6,
19+
20+
// IDXGIDeviceSubObject
21+
GetDevice = 7,
22+
23+
// IDXGISwapChain
24+
Present = 8,
25+
GetBuffer = 9,
26+
SetFullscreenState = 10,
27+
GetFullscreenState = 11,
28+
GetDesc = 12,
29+
ResizeBuffers = 13,
30+
ResizeTarget = 14,
31+
GetContainingOutput = 15,
32+
GetFrameStatistics = 16,
33+
GetLastPresentCount = 17,
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace DirectX.Direct3D11.Overlay
6+
{
7+
8+
public interface IComponent
9+
{
10+
/// <summary>
11+
/// Gets the name of this component.
12+
/// </summary>
13+
/// <value>The name.</value>
14+
string Name { get; set; }
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using DirectX.Direct3D.Core.Drawing;
5+
using DirectX.Direct3D.Core.Memory;
6+
using SharpDX;
7+
using SharpDX.Direct3D11;
8+
9+
namespace DirectX.Direct3D11.Overlay
10+
{
11+
internal class OverlayRenderer : DisposableComponent
12+
{
13+
public List<IOverlay> Overlays { get; set; } = new List<IOverlay>();
14+
private Device _device;
15+
private Texture2D _renderTarget;
16+
private RenderTargetView _renderTargetView;
17+
private DXSprite _spriteEngine;
18+
Dictionary<string, DXFont> _fontCache = new Dictionary<string, DXFont>();
19+
private DeviceContext _deviceContext;
20+
//private readonly Dictionary<string, Font> _fontCache = new Dictionary<string, Font>();
21+
public bool DeferredContext
22+
{
23+
get => _deviceContext.TypeInfo == DeviceContextType.Deferred;
24+
}
25+
26+
27+
28+
public Device Device => _device;
29+
30+
private bool _isInitialized;
31+
private bool _isInitializing;
32+
33+
internal OverlayRenderer()
34+
{
35+
}
36+
37+
private void EnsureInitialized()
38+
{
39+
System.Diagnostics.Debug.Assert(_isInitialized);
40+
}
41+
42+
internal bool Initialize(SharpDX.DXGI.SwapChain swapChain)
43+
{
44+
return Initialize(swapChain.GetDevice<Device>(), swapChain.GetBackBuffer<Texture2D>(0));
45+
}
46+
47+
internal bool Initialize(Device device, Texture2D renderTarget)
48+
{
49+
if (_isInitializing)
50+
{
51+
return false;
52+
}
53+
54+
_isInitializing = true;
55+
56+
try
57+
{
58+
_device = device;
59+
_renderTarget = renderTarget;
60+
try
61+
{
62+
_deviceContext = ToDispose(new DeviceContext(_device));
63+
}
64+
catch(SharpDXException)
65+
{
66+
_deviceContext = _device.ImmediateContext;
67+
}
68+
69+
_renderTargetView = ToDispose(new RenderTargetView(_device, _renderTarget));
70+
_spriteEngine = new DXSprite(_device, _deviceContext);
71+
if(!_spriteEngine.Initialize())
72+
{
73+
return false;
74+
}
75+
76+
InitializeResources();
77+
78+
_isInitialized = true;
79+
}
80+
finally
81+
{
82+
_isInitializing = true;
83+
}
84+
return true;
85+
}
86+
87+
private void InitializeResources()
88+
{
89+
foreach (var overlay in Overlays)
90+
{
91+
foreach (var overlayElement in overlay.Elements)
92+
{
93+
if (overlayElement is TextOverlay textOverlay)
94+
{
95+
GetOverlayFont(textOverlay);
96+
}
97+
}
98+
}
99+
}
100+
101+
private DXFont GetOverlayFont(TextOverlay textOverlay)
102+
{
103+
string fontKey =
104+
$"{textOverlay.Font.Name}{textOverlay.Font.Size}{textOverlay.Font.Style}{textOverlay.AntiAliased}";
105+
106+
if (!_fontCache.TryGetValue(fontKey, out DXFont overlayFont))
107+
{
108+
overlayFont = ToDispose(new DXFont(_device, _deviceContext));
109+
overlayFont.Initialize(textOverlay.Font.Name, textOverlay.Font.Size, textOverlay.Font.Style, textOverlay.AntiAliased);
110+
_fontCache[fontKey] = overlayFont;
111+
}
112+
return overlayFont;
113+
}
114+
115+
public void DrawFrame()
116+
{
117+
EnsureInitialized();
118+
119+
BeginFrame();
120+
121+
foreach (var overlay in Overlays)
122+
{
123+
foreach (var overlayElement in overlay.Elements)
124+
{
125+
if (overlayElement.Hidden)
126+
{
127+
continue;
128+
}
129+
130+
if (overlayElement is TextOverlay textOverlay)
131+
{
132+
var font = GetOverlayFont(textOverlay);
133+
if (font != null && !string.IsNullOrEmpty(textOverlay.Text))
134+
{
135+
_spriteEngine.DrawString(textOverlay.Location.X, textOverlay.Location.Y, textOverlay.Text,
136+
textOverlay.Color, font);
137+
}
138+
}
139+
}
140+
}
141+
142+
EndFrame();
143+
}
144+
145+
private void BeginFrame()
146+
{
147+
SharpDX.Mathematics.Interop.RawViewportF[] viewport =
148+
{
149+
new ViewportF(0, 0, _renderTarget.Description.Width, _renderTarget.Description.Height, 0, 1)
150+
};
151+
_deviceContext.Rasterizer.SetViewports(viewport);
152+
_deviceContext.OutputMerger.SetTargets(_renderTargetView);
153+
}
154+
155+
private void EndFrame()
156+
{
157+
if(DeferredContext)
158+
{
159+
var commands = _deviceContext.FinishCommandList(true);
160+
_device.ImmediateContext.ExecuteCommandList(commands, true);
161+
commands.Dispose();
162+
}
163+
}
164+
protected override void Dispose(bool disposing)
165+
{
166+
_device = null;
167+
}
168+
}
169+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Runtime.InteropServices;
5+
using System.Text;
6+
7+
namespace DirectX.Direct3D11.Overlay
8+
{
9+
/// <summary>
10+
/// Provides a safe handle around a block of unmanaged memory.
11+
/// </summary>
12+
public class SafeHGlobal : SafeHandle
13+
{
14+
/// <summary>
15+
/// When overridden in a derived class, gets a value indicating whether the handle value is invalid.
16+
/// </summary>
17+
/// <returns>true if the handle value is invalid; otherwise, false.</returns>
18+
public override bool IsInvalid
19+
{
20+
get { return handle == IntPtr.Zero; }
21+
}
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="SafeHGlobal"/> class.
25+
/// </summary>
26+
/// <param name="sizeInBytes">The size of the block of memory to allocate, in bytes.</param>
27+
public SafeHGlobal(int sizeInBytes)
28+
: base(Marshal.AllocHGlobal(sizeInBytes), true)
29+
{
30+
}
31+
32+
/// <summary>
33+
/// When overridden in a derived class, executes the code required to free the handle.
34+
/// </summary>
35+
/// <returns>
36+
/// true if the handle is released successfully; otherwise, in the event of a catastrophic failure, false. In this case, it generates a releaseHandleFailed MDA Managed Debugging Assistant.
37+
/// </returns>
38+
protected override bool ReleaseHandle()
39+
{
40+
Marshal.FreeHGlobal(handle);
41+
return true;
42+
}
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Drawing;
4+
using System.Runtime.InteropServices;
5+
using DirectX.Direct3D.Core;
6+
using CoreHook;
7+
using DirectX.Direct3D.Core.Drawing;
8+
using SharpDX;
9+
using SharpDX.Direct3D9;
10+
using Color = System.Drawing.Color;
11+
using Font = SharpDX.Direct3D9.Font;
12+
using Rectangle = SharpDX.Rectangle;
13+
14+
namespace DirectX.Direct3D9.Overlay
15+
{
16+
internal class Direct3DHookModule : Direct3DHook
17+
{
18+
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
19+
unsafe delegate int IDirect3DDevice9_PresentDelegate(IntPtr device, Rectangle* sourceRectangle,
20+
Rectangle* destRectangle, IntPtr destWindowOverride, IntPtr dirtyRegion);
21+
22+
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
23+
private delegate int IDirect3DDevice9_EndSceneDelegate(IntPtr device);
24+
25+
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
26+
private delegate int IDirect3DDevice9_ResetDelegate(IntPtr device, ref PresentParameters parameters);
27+
28+
private IHook<IDirect3DDevice9_PresentDelegate> _d3DPresentHook;
29+
private IHook<IDirect3DDevice9_EndSceneDelegate> _d3DEndSceneHook;
30+
private IHook<IDirect3DDevice9_ResetDelegate> _d3DResetHook;
31+
32+
private OverlayRenderer _overlayRenderer;
33+
private List<IntPtr> _d3DDeviceFunctions = new List<IntPtr>();
34+
private const int D3DDevice9FunctionCount = 119;
35+
private bool _isUsingPresentHook = false;
36+
37+
public override unsafe void CreateHooks()
38+
{
39+
_d3DDeviceFunctions = new List<IntPtr>();
40+
41+
using (var direct3D = new SharpDX.Direct3D9.Direct3D())
42+
{
43+
using (var device = new Device(direct3D, 0, DeviceType.NullReference, IntPtr.Zero,
44+
CreateFlags.HardwareVertexProcessing,
45+
new PresentParameters { BackBufferWidth = 1, BackBufferHeight = 1, DeviceWindowHandle = IntPtr.Zero }))
46+
{
47+
_d3DDeviceFunctions.AddRange(ReadVTableAddresses(device.NativePointer, D3DDevice9FunctionCount));
48+
}
49+
}
50+
51+
// Create the hooks for our target Direct3D Device functions.
52+
_d3DEndSceneHook = HookFactory.CreateHook<IDirect3DDevice9_EndSceneDelegate>(
53+
_d3DDeviceFunctions[(int)FunctionOrdinals.EndScene],
54+
Detour_EndScene,
55+
this);
56+
57+
_d3DPresentHook = HookFactory.CreateHook<IDirect3DDevice9_PresentDelegate>(
58+
_d3DDeviceFunctions[(int) FunctionOrdinals.Present],
59+
Detour_Present,
60+
this);
61+
62+
_d3DResetHook = HookFactory.CreateHook<IDirect3DDevice9_ResetDelegate>(
63+
_d3DDeviceFunctions[(int)FunctionOrdinals.Reset],
64+
Detour_Reset,
65+
this);
66+
67+
// Add the Frames Per Second overlay.
68+
Overlays = new List<IOverlay>
69+
{
70+
new Direct3D.Core.Drawing.Overlay
71+
{
72+
Elements =
73+
{
74+
new FramesPerSecondOverlay(new System.Drawing.Font("Arial", 16, FontStyle.Bold))
75+
{
76+
Location = new System.Drawing.Point(25, 25),
77+
Color = Color.Red,
78+
AntiAliased = true,
79+
Text = "{0:N0} FPS"
80+
}
81+
},
82+
Hidden = false
83+
}
84+
};
85+
86+
// Enable the hooks for all threads except the current thread.
87+
_d3DEndSceneHook.ThreadACL.SetExclusiveACL(new int[1]);
88+
_d3DPresentHook.ThreadACL.SetExclusiveACL(new int[1]);
89+
_d3DResetHook.ThreadACL.SetExclusiveACL(new int[1]);
90+
}
91+
92+
private static IEnumerable<IntPtr> ReadVTableAddresses(IntPtr vTableAddress, int vTableFunctionCount)
93+
{
94+
IntPtr[] addresses = new IntPtr[vTableFunctionCount];
95+
IntPtr vTable = Marshal.ReadIntPtr(vTableAddress);
96+
for (var i = 0; i < vTableFunctionCount; ++i)
97+
{
98+
addresses[i] = Marshal.ReadIntPtr(vTable, i * IntPtr.Size);
99+
}
100+
return addresses;
101+
}
102+
103+
private unsafe int Detour_Present(
104+
IntPtr device,
105+
Rectangle* sourceRectangle,
106+
Rectangle* destRectangle,
107+
IntPtr destWindowOverride,
108+
IntPtr dirtyRegion)
109+
{
110+
_isUsingPresentHook = true;
111+
112+
Device d3Device = (Device)device;
113+
DrawFramesPerSecond(d3Device);
114+
115+
return _d3DPresentHook.Original(device, sourceRectangle, destRectangle, destWindowOverride, dirtyRegion);
116+
}
117+
118+
private int Detour_EndScene(IntPtr direct3DDevice)
119+
{
120+
Device device = (Device)direct3DDevice;
121+
122+
if (!_isUsingPresentHook)
123+
{
124+
DrawFramesPerSecond(device);
125+
}
126+
127+
device.EndScene();
128+
129+
return Result.Ok.Code;
130+
}
131+
132+
private int Detour_Reset(IntPtr direct3DDevice, ref PresentParameters parameters)
133+
{
134+
_overlayRenderer?.ResetDeviceResources();
135+
136+
return _d3DResetHook.Original(direct3DDevice, ref parameters);
137+
}
138+
139+
private void DrawFramesPerSecond(Device device)
140+
{
141+
Capture(device);
142+
}
143+
144+
private void Capture(Device device)
145+
{
146+
try
147+
{
148+
// Draw any overlays that have been added to the global list.
149+
var displayOverlays = Overlays;
150+
if (_overlayRenderer == null ||
151+
_overlayRenderer.Device.NativePointer != device.NativePointer ||
152+
PendingUpdate)
153+
{
154+
if (_overlayRenderer != null)
155+
{
156+
RemoveAndDispose(ref _overlayRenderer);
157+
}
158+
159+
_overlayRenderer = ToDispose((new OverlayRenderer()));
160+
_overlayRenderer.Overlays.AddRange(displayOverlays);
161+
_overlayRenderer.Initialize(device);
162+
PendingUpdate = false;
163+
}
164+
165+
if (_overlayRenderer != null)
166+
{
167+
foreach (var overlay in _overlayRenderer.Overlays)
168+
{
169+
overlay.OnFrame();
170+
}
171+
172+
_overlayRenderer.DrawFrame();
173+
}
174+
}
175+
catch (Exception e)
176+
{
177+
System.Diagnostics.Debug.WriteLine($"{e}");
178+
}
179+
}
180+
181+
}
182+
}

‎src/DirectX.Direct3D9.Overlay/DirectX.Direct3D9.Overlay.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,8 @@
2020
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
2121
</ItemGroup>
2222

23+
<ItemGroup>
24+
<ProjectReference Include="..\DirectX.Direct3D.Core\DirectX.Direct3D.Core.csproj" />
25+
</ItemGroup>
26+
2327
</Project>
+6-118
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Runtime.InteropServices;
4-
using CoreHook;
5-
using SharpDX;
6-
using SharpDX.Direct3D9;
1+
using CoreHook;
2+
using DirectX.Direct3D.Core;
73

84
namespace DirectX.Direct3D9.Overlay
95
{
106
public class EntryPoint : IEntryPoint
117
{
12-
private List<IntPtr> _d3DDeviceFunctions = new List<IntPtr>();
13-
private const int D3DDevice9FunctionCount = 119;
14-
15-
private Font _framesPerSecondFont;
16-
17-
private int _frameCount;
18-
private int _lastTickCount;
19-
private float _lastFrameRate;
8+
private Direct3DHook _direct3DHook;
209

2110
public EntryPoint(IContext context) { }
2211

@@ -29,111 +18,10 @@ public void Run(IContext context)
2918
}
3019
}
3120

32-
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
33-
unsafe delegate int IDirect3DDevice9_PresentDelegate(IntPtr device, Rectangle* sourceRectangle,
34-
Rectangle* destRectangle, IntPtr destWindowOverride, IntPtr dirtyRegion);
35-
36-
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
37-
private delegate int IDirect3DDevice9_EndSceneDelegate(IntPtr device);
38-
39-
private IHook<IDirect3DDevice9_PresentDelegate> _d3DPresentHook;
40-
private IHook<IDirect3DDevice9_EndSceneDelegate> _d3DEndSceneHook;
41-
42-
public unsafe void InitializeDeviceHook()
43-
{
44-
_d3DDeviceFunctions = new List<IntPtr>();
45-
46-
using (var direct3D = new Direct3D())
47-
{
48-
using (var device = new Device(direct3D, 0, DeviceType.NullReference, IntPtr.Zero,
49-
CreateFlags.HardwareVertexProcessing,
50-
new PresentParameters { BackBufferWidth = 1, BackBufferHeight = 1, DeviceWindowHandle = IntPtr.Zero }))
51-
{
52-
_d3DDeviceFunctions.AddRange(ReadVTableAddresses(device.NativePointer, D3DDevice9FunctionCount));
53-
}
54-
}
55-
56-
_d3DEndSceneHook = HookFactory.CreateHook<IDirect3DDevice9_EndSceneDelegate>(
57-
_d3DDeviceFunctions[(int)FunctionOrdinals.EndScene],
58-
Detour_EndScene,
59-
this);
60-
61-
_d3DPresentHook = HookFactory.CreateHook<IDirect3DDevice9_PresentDelegate>(
62-
_d3DDeviceFunctions[(int)FunctionOrdinals.Present],
63-
Detour_Present,
64-
this);
65-
66-
_d3DEndSceneHook.ThreadACL.SetExclusiveACL(new int[1]);
67-
_d3DPresentHook.ThreadACL.SetExclusiveACL(new int[1]);
68-
}
69-
70-
private static IEnumerable<IntPtr> ReadVTableAddresses(IntPtr vTableAddress, int vTableFunctionCount)
71-
{
72-
IntPtr[] addresses = new IntPtr[vTableFunctionCount];
73-
IntPtr vTable = Marshal.ReadIntPtr(vTableAddress);
74-
for (var i = 0; i < vTableFunctionCount; ++i)
75-
{
76-
addresses[i] = Marshal.ReadIntPtr(vTable, i * IntPtr.Size);
77-
}
78-
return addresses;
79-
}
80-
81-
private unsafe int Detour_Present(
82-
IntPtr device,
83-
Rectangle* sourceRectangle,
84-
Rectangle* destRectangle,
85-
IntPtr destWindowOverride,
86-
IntPtr dirtyRegion)
21+
public void InitializeDeviceHook()
8722
{
88-
_frameCount++;
89-
90-
return _d3DPresentHook.Original(device, sourceRectangle, destRectangle, destWindowOverride, dirtyRegion);
91-
}
92-
93-
private int Detour_EndScene(IntPtr direct3DDevice)
94-
{
95-
Device device = (Device)direct3DDevice;
96-
97-
DrawFramesPerSecond(device);
98-
99-
device.EndScene();
100-
101-
return Result.Ok.Code;
102-
}
103-
104-
private void DrawFramesPerSecond(Device device)
105-
{
106-
try
107-
{
108-
var tickCount = Environment.TickCount;
109-
if (Math.Abs(tickCount - _lastTickCount) > 1000)
110-
{
111-
_lastFrameRate = (float)_frameCount * 1000 / Math.Abs(tickCount - _lastTickCount);
112-
_frameCount = 0;
113-
_lastTickCount = tickCount;
114-
}
115-
if (_framesPerSecondFont == null)
116-
{
117-
_framesPerSecondFont = new Font(device, new FontDescription
118-
{
119-
Height = 20,
120-
FaceName = "Arial",
121-
Italic = false,
122-
Width = 0,
123-
MipLevels = 1,
124-
CharacterSet = FontCharacterSet.Default,
125-
OutputPrecision = FontPrecision.Default,
126-
Quality = FontQuality.ClearTypeNatural,
127-
PitchAndFamily = FontPitchAndFamily.Default | FontPitchAndFamily.DontCare,
128-
Weight = FontWeight.Bold
129-
});
130-
}
131-
132-
_framesPerSecondFont.DrawText(null, $"{_lastFrameRate:N0} FPS", 0, 0, new ColorBGRA(244, 66, 86, 255));
133-
}
134-
catch (Exception)
135-
{
136-
}
23+
_direct3DHook = new Direct3DHookModule();
24+
_direct3DHook.CreateHooks();
13725
}
13826
}
13927
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using System.Collections.Generic;
2+
using DirectX.Direct3D.Core.Drawing;
3+
using DirectX.Direct3D.Core.Memory;
4+
using SharpDX;
5+
using SharpDX.Direct3D9;
6+
7+
namespace DirectX.Direct3D9.Overlay
8+
{
9+
internal class OverlayRenderer : DisposableComponent
10+
{
11+
public List<IOverlay> Overlays { get; set; } = new List<IOverlay>();
12+
private readonly Dictionary<string, Font> _fontCache = new Dictionary<string, Font>();
13+
14+
private Device _device;
15+
private Sprite _sprite;
16+
17+
public Device Device => _device;
18+
19+
private bool _isInitialized;
20+
private bool _isInitializing;
21+
22+
internal OverlayRenderer()
23+
{
24+
}
25+
26+
private void EnsureInitialized()
27+
{
28+
System.Diagnostics.Debug.Assert(_isInitialized);
29+
}
30+
31+
internal bool Initialize(Device device)
32+
{
33+
if (_isInitializing)
34+
{
35+
return false;
36+
}
37+
38+
_isInitializing = true;
39+
40+
try
41+
{
42+
_device = device;
43+
44+
_sprite = ToDispose(new Sprite(device));
45+
46+
InitializeResources();
47+
48+
_isInitialized = true;
49+
}
50+
finally
51+
{
52+
_isInitializing = true;
53+
}
54+
return true;
55+
}
56+
57+
private void InitializeResources()
58+
{
59+
foreach (var overlay in Overlays)
60+
{
61+
foreach (var overlayElement in overlay.Elements)
62+
{
63+
if(overlayElement is TextOverlay textOverlay)
64+
{
65+
GetOverlayFont(textOverlay);
66+
}
67+
}
68+
}
69+
}
70+
71+
public void DrawFrame()
72+
{
73+
EnsureInitialized();
74+
75+
BeginFrame();
76+
77+
foreach (var overlay in Overlays)
78+
{
79+
foreach (var overlayElement in overlay.Elements)
80+
{
81+
if (overlayElement is TextOverlay textOverlay)
82+
{
83+
var font = GetOverlayFont(textOverlay);
84+
if (font != null && !string.IsNullOrEmpty(textOverlay.Text))
85+
{
86+
font.DrawText(_sprite, textOverlay.Text, textOverlay.Location.X, textOverlay.Location.Y,
87+
new ColorBGRA(textOverlay.Color.R, textOverlay.Color.G, textOverlay.Color.B,
88+
textOverlay.Color.A));
89+
}
90+
}
91+
}
92+
}
93+
94+
EndFrame();
95+
}
96+
97+
private void BeginFrame()
98+
{
99+
_sprite.Begin(SpriteFlags.AlphaBlend);
100+
}
101+
102+
private void EndFrame()
103+
{
104+
_sprite.End();
105+
}
106+
107+
private Font GetOverlayFont(TextOverlay textOverlay)
108+
{
109+
string fontKey =
110+
$"{textOverlay.Font.Name}{textOverlay.Font.Size}{textOverlay.Font.Style}{textOverlay.AntiAliased}";
111+
112+
if (!_fontCache.TryGetValue(fontKey, out Font overlayFont))
113+
{
114+
overlayFont = ToDispose(new Font(_device, new FontDescription()
115+
{
116+
FaceName = textOverlay.Font.Name,
117+
Italic = (textOverlay.Font.Style & System.Drawing.FontStyle.Italic) == System.Drawing.FontStyle.Italic,
118+
Quality = (textOverlay.AntiAliased ? FontQuality.Antialiased : FontQuality.Default),
119+
Weight = ((textOverlay.Font.Style & System.Drawing.FontStyle.Bold) == System.Drawing.FontStyle.Bold) ? FontWeight.Bold : FontWeight.Normal,
120+
Height = (int)textOverlay.Font.SizeInPoints
121+
}));
122+
_fontCache[fontKey] = overlayFont;
123+
}
124+
return overlayFont;
125+
}
126+
127+
public void ResetDeviceResources()
128+
{
129+
try
130+
{
131+
foreach (var font in _fontCache)
132+
{
133+
font.Value.OnLostDevice();
134+
}
135+
136+
_sprite?.OnLostDevice();
137+
}
138+
catch { }
139+
}
140+
141+
protected override void Dispose(bool disposing)
142+
{
143+
_device = null;
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)
Please sign in to comment.