using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using ScreenCapture.NET.Downscale; using SharpGen.Runtime; using Vortice.Direct3D9; namespace ScreenCapture.NET; /// /// Represents a ScreenCapture using DirectX 9. /// https://learn.microsoft.com/en-us/windows/win32/api/d3d9/nf-d3d9-idirect3ddevice9-getfrontbufferdata /// // ReSharper disable once InconsistentNaming public sealed class DX9ScreenCapture : AbstractScreenCapture { #region Properties & Fields private readonly object _captureLock = new(); private readonly IDirect3D9 _direct3D9; private IDirect3DDevice9? _device; private IDirect3DSurface9? _surface; private byte[]? _buffer; private int _stride; #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The D3D9 instance used. /// The to duplicate. internal DX9ScreenCapture(IDirect3D9 direct3D9, Display display) : base(display) { this._direct3D9 = direct3D9; Restart(); } #endregion #region Methods /// protected override bool PerformScreenCapture() { bool result = false; lock (_captureLock) { if ((_device == null) || (_surface == null) || (_buffer == null)) { Restart(); return false; } try { _device.GetFrontBufferData(0, _surface); LockedRectangle dr = _surface.LockRect(LockFlags.NoSystemLock | LockFlags.ReadOnly); nint ptr = dr.DataPointer; for (int y = 0; y < Display.Height; y++) { Marshal.Copy(ptr, _buffer, y * _stride, _stride); ptr += dr.Pitch; } _surface.UnlockRect(); result = true; } catch (SharpGenException dxException) { if (dxException.ResultCode == Result.AccessDenied) { try { Restart(); } catch { Thread.Sleep(100); } } } } return result; } /// protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) { if (_buffer == null) return; using IDisposable @lock = captureZone.Lock(); { if (captureZone.DownscaleLevel == 0) CopyZone(captureZone, buffer); else DownscaleZone(captureZone, buffer); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CopyZone(CaptureZone captureZone, in Span buffer) { ReadOnlySpan source = MemoryMarshal.Cast(_buffer); Span target = MemoryMarshal.Cast(buffer); int offsetX = captureZone.X; int offsetY = captureZone.Y; int width = captureZone.Width; int height = captureZone.Height; for (int y = 0; y < height; y++) { int sourceOffset = ((y + offsetY) * Display.Width) + offsetX; int targetOffset = y * width; source.Slice(sourceOffset, width).CopyTo(target.Slice(targetOffset, width)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DownscaleZone(CaptureZone captureZone, in Span buffer) { ReadOnlySpan source = _buffer; Span target = buffer; int blockSize = 1 << captureZone.DownscaleLevel; int offsetX = captureZone.X; int offsetY = captureZone.Y; int width = captureZone.Width; int height = captureZone.Height; int stride = captureZone.Stride; int bpp = captureZone.ColorFormat.BytesPerPixel; int unscaledWith = captureZone.UnscaledWidth; Span scaleBuffer = stackalloc byte[bpp]; for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) { AverageByteSampler.Sample(new SamplerInfo((x + offsetX) * blockSize, (y + offsetY) * blockSize, blockSize, blockSize, unscaledWith, bpp, source), scaleBuffer); int targetOffset = (y * stride) + (x * bpp); // DarthAffe 07.09.2023: Unroll as optimization since we know it's always 4 bpp - not ideal but it does quite a lot target[targetOffset] = scaleBuffer[0]; target[targetOffset + 1] = scaleBuffer[1]; target[targetOffset + 2] = scaleBuffer[2]; target[targetOffset + 3] = scaleBuffer[3]; } } /// public override void Restart() { base.Restart(); lock (_captureLock) { DisposeDX(); try { PresentParameters presentParameters = new() { BackBufferWidth = Display.Width, BackBufferHeight = Display.Height, Windowed = true, SwapEffect = SwapEffect.Discard }; _device = _direct3D9.CreateDevice(Display.Index, DeviceType.Hardware, IntPtr.Zero, CreateFlags.SoftwareVertexProcessing, presentParameters); _surface = _device.CreateOffscreenPlainSurface(Display.Width, Display.Height, Format.A8R8G8B8, Pool.Scratch); _stride = Display.Width * ColorBGRA.ColorFormat.BytesPerPixel; _buffer = new byte[Display.Height * _stride]; } catch { DisposeDX(); } } } /// protected override void Dispose(bool disposing) { base.Dispose(disposing); lock (_captureLock) DisposeDX(); } private void DisposeDX() { try { try { _surface?.Dispose(); } catch { /**/} try { _device?.Dispose(); } catch { /**/} _buffer = null; _device = null; _surface = null; } catch { /**/ } } #endregion }