Skip to content

Commit 2667f84

Browse files
committed
Added DX9 capture
1 parent 6919cf7 commit 2667f84

File tree

8 files changed

+616
-5
lines changed

8 files changed

+616
-5
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
using System.Threading;
5+
using ScreenCapture.NET.Downscale;
6+
using SharpGen.Runtime;
7+
using Vortice.Direct3D9;
8+
9+
namespace ScreenCapture.NET;
10+
11+
/// <summary>
12+
/// Represents a ScreenCapture using DirectX 11 desktop duplicaton.
13+
/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api
14+
/// </summary>
15+
// ReSharper disable once InconsistentNaming
16+
public sealed class DX9ScreenCapture : AbstractScreenCapture<ColorBGRA>
17+
{
18+
#region Properties & Fields
19+
20+
private readonly object _captureLock = new();
21+
22+
private readonly IDirect3D9 _direct3D9;
23+
private IDirect3DDevice9? _device;
24+
private IDirect3DSurface9? _surface;
25+
private byte[]? _buffer;
26+
private int _stride;
27+
28+
#endregion
29+
30+
#region Constructors
31+
32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="DX9ScreenCapture"/> class.
34+
/// </summary>
35+
/// <param name="display">The <see cref="Display"/> to duplicate.</param>
36+
public DX9ScreenCapture(IDirect3D9 direct3D9, Display display)
37+
: base(display)
38+
{
39+
this._direct3D9 = direct3D9;
40+
41+
Restart();
42+
}
43+
44+
#endregion
45+
46+
#region Methods
47+
48+
protected override bool PerformScreenCapture()
49+
{
50+
bool result = false;
51+
lock (_captureLock)
52+
{
53+
if ((_device == null) || (_surface == null) || (_buffer == null))
54+
{
55+
Restart();
56+
return false;
57+
}
58+
59+
try
60+
{
61+
_device.GetFrontBufferData(0, _surface);
62+
63+
LockedRectangle dr = _surface.LockRect(LockFlags.NoSystemLock | LockFlags.ReadOnly);
64+
65+
nint ptr = dr.DataPointer;
66+
for (int y = 0; y < Display.Height; y++)
67+
{
68+
Marshal.Copy(ptr, _buffer, y * _stride, _stride);
69+
ptr += dr.Pitch;
70+
}
71+
72+
_surface.UnlockRect();
73+
74+
result = true;
75+
}
76+
catch (SharpGenException dxException)
77+
{
78+
if (dxException.ResultCode == Result.AccessDenied)
79+
{
80+
try
81+
{
82+
Restart();
83+
}
84+
catch { Thread.Sleep(100); }
85+
}
86+
}
87+
}
88+
89+
return result;
90+
}
91+
92+
protected override void PerformCaptureZoneUpdate(CaptureZone<ColorBGRA> captureZone, in Span<byte> buffer)
93+
{
94+
if (_buffer == null) return;
95+
96+
using IDisposable @lock = captureZone.Lock();
97+
{
98+
if (captureZone.DownscaleLevel == 0)
99+
CopyZone(captureZone, buffer);
100+
else
101+
DownscaleZone(captureZone, buffer);
102+
}
103+
}
104+
105+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
106+
private void CopyZone(CaptureZone<ColorBGRA> captureZone, in Span<byte> buffer)
107+
{
108+
ReadOnlySpan<ColorBGRA> source = MemoryMarshal.Cast<byte, ColorBGRA>(_buffer);
109+
Span<ColorBGRA> target = MemoryMarshal.Cast<byte, ColorBGRA>(buffer);
110+
111+
int offsetX = captureZone.X;
112+
int offsetY = captureZone.Y;
113+
int width = captureZone.Width;
114+
int height = captureZone.Height;
115+
116+
for (int y = 0; y < height; y++)
117+
{
118+
int sourceOffset = ((y + offsetY) * Display.Width) + offsetX;
119+
int targetOffset = y * width;
120+
121+
source.Slice(sourceOffset, width).CopyTo(target.Slice(targetOffset, width));
122+
}
123+
}
124+
125+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
126+
private void DownscaleZone(CaptureZone<ColorBGRA> captureZone, in Span<byte> buffer)
127+
{
128+
ReadOnlySpan<byte> source = _buffer;
129+
Span<byte> target = buffer;
130+
131+
int blockSize = captureZone.DownscaleLevel switch
132+
{
133+
1 => 2,
134+
2 => 4,
135+
3 => 8,
136+
4 => 16,
137+
5 => 32,
138+
6 => 64,
139+
7 => 128,
140+
8 => 256,
141+
_ => (int)Math.Pow(2, captureZone.DownscaleLevel),
142+
};
143+
144+
int offsetX = captureZone.X;
145+
int offsetY = captureZone.Y;
146+
int width = captureZone.Width;
147+
int height = captureZone.Height;
148+
int stride = captureZone.Stride;
149+
int bpp = captureZone.ColorFormat.BytesPerPixel;
150+
int unscaledWith = captureZone.UnscaledWidth;
151+
152+
Span<byte> scaleBuffer = stackalloc byte[bpp];
153+
for (int y = 0; y < height; y++)
154+
for (int x = 0; x < width; x++)
155+
{
156+
AverageByteSampler.Sample(new SamplerInfo<byte>((x + offsetX) * blockSize, (y + offsetY) * blockSize, blockSize, blockSize, unscaledWith, bpp, source), scaleBuffer);
157+
158+
int targetOffset = (y * stride) + (x * bpp);
159+
160+
// DarthAffe 07.09.2023: Unroll as optimization since we know it's always 4 bpp - not ideal but it does quite a lot
161+
target[targetOffset] = scaleBuffer[0];
162+
target[targetOffset + 1] = scaleBuffer[1];
163+
target[targetOffset + 2] = scaleBuffer[2];
164+
target[targetOffset + 3] = scaleBuffer[3];
165+
}
166+
}
167+
168+
/// <inheritdoc />
169+
public override void Restart()
170+
{
171+
base.Restart();
172+
173+
lock (_captureLock)
174+
{
175+
DisposeDX();
176+
177+
try
178+
{
179+
PresentParameters presentParameters = new()
180+
{
181+
BackBufferWidth = Display.Width,
182+
BackBufferHeight = Display.Height,
183+
Windowed = true,
184+
SwapEffect = SwapEffect.Discard
185+
};
186+
_device = _direct3D9.CreateDevice(Display.Index, DeviceType.Hardware, IntPtr.Zero, CreateFlags.SoftwareVertexProcessing, presentParameters);
187+
_surface = _device.CreateOffscreenPlainSurface(Display.Width, Display.Height, Format.A8R8G8B8, Pool.Scratch);
188+
_stride = Display.Width * ColorBGRA.ColorFormat.BytesPerPixel;
189+
_buffer = new byte[Display.Height * _stride];
190+
}
191+
catch
192+
{
193+
DisposeDX();
194+
}
195+
}
196+
}
197+
198+
protected override void Dispose(bool disposing)
199+
{
200+
base.Dispose(disposing);
201+
DisposeDX();
202+
}
203+
204+
private void DisposeDX()
205+
{
206+
try
207+
{
208+
try { _surface?.Dispose(); } catch { /**/}
209+
try { _device?.Dispose(); } catch { /**/}
210+
_buffer = null;
211+
_device = null;
212+
_surface = null;
213+
}
214+
catch { /**/ }
215+
}
216+
217+
#endregion
218+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Vortice.Direct3D9;
4+
5+
namespace ScreenCapture.NET;
6+
7+
/// <summary>
8+
/// Represents a <see cref="IScreenCaptureService"/> using the <see cref="DX9ScreenCapture"/>.
9+
/// </summary>
10+
public class DX9ScreenCaptureService : IScreenCaptureService
11+
{
12+
#region Properties & Fields
13+
14+
private readonly IDirect3D9 _direct3D9;
15+
private readonly Dictionary<Display, DX9ScreenCapture> _screenCaptures = new();
16+
17+
private bool _isDisposed;
18+
19+
#endregion
20+
21+
#region Constructors
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="DX9ScreenCaptureService"/> class.
25+
/// </summary>
26+
public DX9ScreenCaptureService()
27+
{
28+
_direct3D9 = D3D9.Direct3DCreate9();
29+
}
30+
31+
~DX9ScreenCaptureService() => Dispose();
32+
33+
#endregion
34+
35+
#region Methods
36+
37+
/// <inheritdoc />
38+
public IEnumerable<GraphicsCard> GetGraphicsCards()
39+
{
40+
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
41+
42+
Dictionary<int, GraphicsCard> graphicsCards = new();
43+
for (int i = 0; i < _direct3D9.AdapterCount; i++)
44+
{
45+
AdapterIdentifier adapterIdentifier = _direct3D9.GetAdapterIdentifier(i);
46+
if (!graphicsCards.ContainsKey(adapterIdentifier.DeviceId))
47+
graphicsCards.Add(adapterIdentifier.DeviceId, new GraphicsCard(i, adapterIdentifier.Description, adapterIdentifier.VendorId, adapterIdentifier.DeviceId));
48+
}
49+
50+
return graphicsCards.Values;
51+
}
52+
53+
/// <inheritdoc />
54+
public IEnumerable<Display> GetDisplays(GraphicsCard graphicsCard)
55+
{
56+
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
57+
58+
for (int i = 0; i < _direct3D9.AdapterCount; i++)
59+
{
60+
AdapterIdentifier adapterIdentifier = _direct3D9.GetAdapterIdentifier(i);
61+
if (adapterIdentifier.DeviceId == graphicsCard.DeviceId)
62+
{
63+
DisplayMode displayMode = _direct3D9.GetAdapterDisplayMode(i);
64+
yield return new Display(i, adapterIdentifier.DeviceName, displayMode.Width, displayMode.Height, Rotation.None, graphicsCard);
65+
}
66+
}
67+
}
68+
69+
/// <inheritdoc />
70+
IScreenCapture IScreenCaptureService.GetScreenCapture(Display display) => GetScreenCapture(display);
71+
public DX9ScreenCapture GetScreenCapture(Display display)
72+
{
73+
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
74+
75+
if (!_screenCaptures.TryGetValue(display, out DX9ScreenCapture? screenCapture))
76+
_screenCaptures.Add(display, screenCapture = new DX9ScreenCapture(_direct3D9, display));
77+
return screenCapture;
78+
}
79+
80+
/// <inheritdoc />
81+
public void Dispose()
82+
{
83+
if (_isDisposed) return;
84+
85+
foreach (DX9ScreenCapture screenCapture in _screenCaptures.Values)
86+
screenCapture.Dispose();
87+
_screenCaptures.Clear();
88+
89+
try { _direct3D9.Dispose(); } catch { /**/ }
90+
91+
GC.SuppressFinalize(this);
92+
93+
_isDisposed = true;
94+
}
95+
96+
#endregion
97+
}

0 commit comments

Comments
 (0)