From 13a85b5d554f90c71ab5082b129c1084f8cc3a31 Mon Sep 17 00:00:00 2001 From: Kris Taeleman Date: Thu, 12 Jan 2023 12:51:50 -0800 Subject: [PATCH 1/8] Replace SharpDX by Vortice and add HDR support --- .../Capture/Dx11ScreenCapture.cs | 266 ++++++++++++++---- HyperionScreenCap/Helper/HyperionTask.cs | 6 +- HyperionScreenCap/HyperionScreenCap.csproj | 52 +++- HyperionScreenCap/Program.cs | 59 +++- HyperionScreenCap/Properties/AssemblyInfo.cs | 4 + HyperionScreenCap/app.config | 10 +- HyperionScreenCap/packages.config | 15 +- 7 files changed, 336 insertions(+), 76 deletions(-) diff --git a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs index 16ac024..33373fc 100644 --- a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs +++ b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs @@ -1,7 +1,8 @@ using HyperionScreenCap.Capture; -using SharpDX; -using SharpDX.Direct3D11; -using SharpDX.DXGI; +using Vortice; +using Vortice.Direct3D; +using Vortice.Direct3D11; +using Vortice.DXGI; using System; using System.Collections.Generic; using System.Diagnostics; @@ -12,9 +13,24 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; +using static Vortice.Direct3D11.D3D11; +using static Vortice.DXGI.DXGI; + + namespace HyperionScreenCap { + + static class MathExt + { + public static T Clamp(this T val, T min, T max) where T : IComparable + { + if (val.CompareTo(min) < 0) return min; + else if (val.CompareTo(max) > 0) return max; + else return val; + } + } + class DX11ScreenCapture : IScreenCapture { private int _adapterIndex; @@ -23,15 +39,15 @@ class DX11ScreenCapture : IScreenCapture private int _maxFps; private int _frameCaptureTimeout; - private Factory1 _factory; - private Adapter _adapter; - private Output _output; - private Output1 _output1; - private SharpDX.Direct3D11.Device _device; - private Texture2D _stagingTexture; - private Texture2D _smallerTexture; - private ShaderResourceView _smallerTextureView; - private OutputDuplication _duplicatedOutput; + private IDXGIFactory2 _factory; + private IDXGIAdapter1 _adapter; + private IDXGIOutput _output; + private IDXGIOutput5 _output5; + private ID3D11Device _device; + private ID3D11Texture2D _stagingTexture; + private ID3D11Texture2D _smallerTexture; + private ID3D11ShaderResourceView _smallerTextureView; + private IDXGIOutputDuplication _duplicatedOutput; private int _scalingFactorLog2; private int _width; private int _height; @@ -40,6 +56,10 @@ class DX11ScreenCapture : IScreenCapture private Stopwatch _captureTimer; private bool _desktopDuplicatorInvalid; private bool _disposed; + private static readonly FeatureLevel[] s_featureLevels = new[] + { + FeatureLevel.Level_11_0 + }; public int CaptureWidth { get; private set; } public int CaptureHeight { get; private set; } @@ -47,22 +67,25 @@ class DX11ScreenCapture : IScreenCapture public static String GetAvailableMonitors() { StringBuilder response = new StringBuilder(); - using ( Factory1 factory = new Factory1() ) + IDXGIFactory2 tempFactory = null; + DXGI.CreateDXGIFactory2(false,out tempFactory); + //using ( IDXGIFactory2 factory = new IDXGIFactory2() ) { int adapterIndex = 0; - foreach(Adapter adapter in factory.Adapters) + while(tempFactory.EnumAdapters(adapterIndex, out IDXGIAdapter adapter) == SharpGen.Runtime.Result.Ok) { response.Append($"Adapter Index {adapterIndex++}: {adapter.Description.Description}\n"); int outputIndex = 0; - foreach(Output output in adapter.Outputs) + while(adapter.EnumOutputs(outputIndex, out IDXGIOutput output) == SharpGen.Runtime.Result.Ok) { response.Append($"\tMonitor Index {outputIndex++}: {output.Description.DeviceName}"); - var desktopBounds = output.Description.DesktopBounds; + var desktopBounds = output.Description.DesktopCoordinates; response.Append($" {desktopBounds.Right - desktopBounds.Left}×{desktopBounds.Bottom - desktopBounds.Top}\n"); } response.Append("\n"); } } + tempFactory.Dispose(); return response.ToString(); } @@ -91,19 +114,19 @@ public void Initialize() else throw new Exception("Invalid scaling factor. Allowed valued are 1, 2, 4, etc."); - // Create DXGI Factory1 - _factory = new Factory1(); - _adapter = _factory.GetAdapter1(_adapterIndex); + // Create DXGI IDXGIFactory2 + DXGI.CreateDXGIFactory2(false,out _factory); + _factory.EnumAdapters1(_adapterIndex, out _adapter); // Create device from Adapter - _device = new SharpDX.Direct3D11.Device(_adapter); + D3D11CreateDevice(_adapter, DriverType.Unknown, /*DeviceCreationFlags.BgraSupport*/ DeviceCreationFlags.None, s_featureLevels, out _device); // Get DXGI.Output - _output = _adapter.GetOutput(_monitorIndex); - _output1 = _output.QueryInterface(); + _adapter.EnumOutputs(_monitorIndex, out _output); + _output5 = _output.QueryInterface(); // Width/Height of desktop to capture - var desktopBounds = _output.Description.DesktopBounds; + var desktopBounds = _output.Description.DesktopCoordinates; _width = desktopBounds.Right - desktopBounds.Left; _height = desktopBounds.Bottom - desktopBounds.Top; @@ -113,35 +136,35 @@ public void Initialize() // Create Staging texture CPU-accessible var stagingTextureDesc = new Texture2DDescription { - CpuAccessFlags = CpuAccessFlags.Read, + CPUAccessFlags = CpuAccessFlags.Read, BindFlags = BindFlags.None, - Format = Format.B8G8R8A8_UNorm, + Format = Format.R16G16B16A16_Float, Width = CaptureWidth, Height = CaptureHeight, - OptionFlags = ResourceOptionFlags.None, + MiscFlags = ResourceOptionFlags.None, MipLevels = 1, ArraySize = 1, SampleDescription = { Count = 1, Quality = 0 }, Usage = ResourceUsage.Staging }; - _stagingTexture = new Texture2D(_device, stagingTextureDesc); + _stagingTexture = _device.CreateTexture2D(stagingTextureDesc); // Create smaller texture to downscale the captured image var smallerTextureDesc = new Texture2DDescription { - CpuAccessFlags = CpuAccessFlags.None, + CPUAccessFlags = CpuAccessFlags.None, BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, - Format = Format.B8G8R8A8_UNorm, + Format = Format.R16G16B16A16_Float, Width = _width, Height = _height, - OptionFlags = ResourceOptionFlags.GenerateMipMaps, + MiscFlags = ResourceOptionFlags.GenerateMips, MipLevels = mipLevels, ArraySize = 1, SampleDescription = { Count = 1, Quality = 0 }, Usage = ResourceUsage.Default }; - _smallerTexture = new Texture2D(_device, smallerTextureDesc); - _smallerTextureView = new ShaderResourceView(_device, _smallerTexture); + _smallerTexture = _device.CreateTexture2D(smallerTextureDesc); + _smallerTextureView = _device.CreateShaderResourceView(_smallerTexture); _minCaptureTime = 1000 / _maxFps; _captureTimer = new Stopwatch(); @@ -153,7 +176,8 @@ public void Initialize() private void InitDesktopDuplicator() { // Duplicate the output - _duplicatedOutput = _output1.DuplicateOutput(_device); + Format[] DesktopFormats = { Format.R16G16B16A16_Float, Format.B8G8R8A8_UNorm }; + _duplicatedOutput = _output5.DuplicateOutput1(_device, DesktopFormats); _desktopDuplicatorInvalid = false; } @@ -175,76 +199,206 @@ public byte[] Capture() private byte[] ManagedCapture() { - SharpDX.DXGI.Resource screenResource = null; - OutputDuplicateFrameInformation duplicateFrameInformation; + IDXGIResource screenResource = null; + OutduplFrameInfo duplicateFrameInformation; try { - try { // Try to get duplicated frame within given time - _duplicatedOutput.AcquireNextFrame(_frameCaptureTimeout, out duplicateFrameInformation, out screenResource); + SharpGen.Runtime.Result res = _duplicatedOutput.AcquireNextFrame(_frameCaptureTimeout, out duplicateFrameInformation, out screenResource); - if ( duplicateFrameInformation.LastPresentTime == 0 && _lastCapturedFrame != null ) - return _lastCapturedFrame; - } - catch ( SharpDXException ex ) - { - if ( ex.ResultCode.Code == SharpDX.DXGI.ResultCode.WaitTimeout.Code && _lastCapturedFrame != null ) + if ( res == SharpGen.Runtime.Result.WaitTimeout.Code && _lastCapturedFrame != null ) return _lastCapturedFrame; - if ( ex.ResultCode.Code == SharpDX.DXGI.ResultCode.AccessLost.Code ) + if (res == new SharpGen.Runtime.Result(0x887A0026)) // ACCESS_LOST + { _desktopDuplicatorInvalid = true; - - throw ex; + return null; + } + if ( duplicateFrameInformation.LastPresentTime == 0 && _lastCapturedFrame != null ) + return _lastCapturedFrame; } // Check if scaling is used if ( CaptureWidth != _width ) { // Copy resource into memory that can be accessed by the CPU - using ( var screenTexture2D = screenResource.QueryInterface() ) - _device.ImmediateContext.CopySubresourceRegion(screenTexture2D, 0, null, _smallerTexture, 0); + using ( var screenTexture2D = screenResource.QueryInterface() ) + _device.ImmediateContext.CopySubresourceRegion(_smallerTexture, 0, 0, 0, 0, screenTexture2D, 0); // Generates the mipmap of the screen _device.ImmediateContext.GenerateMips(_smallerTextureView); // Copy the mipmap of smallerTexture (size/ scalingFactor) to the staging texture: 1 for /2, 2 for /4...etc - _device.ImmediateContext.CopySubresourceRegion(_smallerTexture, _scalingFactorLog2, null, _stagingTexture, 0); + _device.ImmediateContext.CopySubresourceRegion(_stagingTexture, 0, 0, 0, 0, _smallerTexture, _scalingFactorLog2); } else { // Copy resource into memory that can be accessed by the CPU - using ( var screenTexture2D = screenResource.QueryInterface() ) + using ( var screenTexture2D = screenResource.QueryInterface() ) _device.ImmediateContext.CopyResource(screenTexture2D, _stagingTexture); } // Get the desktop capture texture - var mapSource = _device.ImmediateContext.MapSubresource(_stagingTexture, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None); - _lastCapturedFrame = ToRGBArray(mapSource); + MappedSubresource mapSource = _device.ImmediateContext.Map(_stagingTexture, 0, MapMode.Read); + _lastCapturedFrame = ToRGBArrayFast(mapSource); return _lastCapturedFrame; } finally { screenResource?.Dispose(); // Fixed OUT_OF_MEMORY issue on AMD Radeon cards. Ignoring all exceptions during unmapping. - try { _device.ImmediateContext.UnmapSubresource(_stagingTexture, 0); } catch { }; + try { _device.ImmediateContext.Unmap(_stagingTexture, 0); } catch { }; // Ignore DXGI_ERROR_INVALID_CALL, DXGI_ERROR_ACCESS_LOST errors since capture is already complete try { _duplicatedOutput.ReleaseFrame(); } catch { } } } + [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)] + static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length); + + public static unsafe void MemCopy(IntPtr ptrSource, ulong[] dest, uint elements) + { + fixed (ulong* ptrDest = &dest[0]) + { + CopyMemory((IntPtr)ptrDest, ptrSource, elements * 8); // 8 bytes per element + } + } + public static float Parse16BitFloat(byte HI, byte LO) + { + // Program assumes ints are at least 16 bits + int fullFloat = ((HI << 8) | LO); + int exponent = (HI & 0b01111110) >> 1; // minor optimisation can be placed here + int mant = fullFloat & 0x01FF; + + // Special values + if (exponent == 0b00111111) // If using constants, shift right by 1 + { + // Check for non or inf + return mant != 0 ? float.NaN : + ((HI & 0x80) == 0 ? float.PositiveInfinity : float.NegativeInfinity); + } + else // normal/denormal values: pad numbers + { + exponent = exponent - 31 + 127; + mant = mant << 14; + Int32 finalFloat = (HI & 0x80) << 24 | (exponent << 23) | mant; + return BitConverter.ToSingle(BitConverter.GetBytes(finalFloat), 0); + } + } + public static float Parse16BitFloat2(byte Hi, byte Lo) + { + int fullFloat = ((Hi << 8) | Lo); + int mant = fullFloat & 0x03ff; // 10 bits mantissa + int exp = fullFloat & 0x7c00; // 5 bits exponent + if( exp == 0x7c00 ) // NaN/Inf + exp = 0x3fc00; // -> NaN/Inf + else if( exp != 0 ) // normalized value + { + exp += 0x1c000; // exp - 15 + 127 + if( mant == 0 && exp > 0x1c400 ) // smooth transition + return BitConverter.ToSingle(BitConverter.GetBytes( ( fullFloat & 0x8000 ) << 16 + | exp << 13 | 0x3ff ), 0); + } + else if( mant != 0 ) // && exp==0 -> subnormal + { + exp = 0x1c400; // make it normal + do { + mant <<= 1; // mantissa * 2 + exp -= 0x400; // decrease exp by 1 + } while( ( mant & 0x400 ) == 0 ); // while not normal + mant &= 0x3ff; // discard subnormal bit + } // else +/-0 -> +/-0 + return BitConverter.ToSingle(BitConverter.GetBytes( // combine all parts + ( fullFloat & 0x8000 ) << 16 // sign << ( 31 - 15 ) + | ( exp | mant ) << 13 ), 0); // value << ( 23 - 10 ) + } /// /// Reads from the memory locations pointed to by the DataBox and saves it into a byte array /// ignoring the alpha component of each pixel. /// /// /// - private byte[] ToRGBArray(DataBox mapSource) + private byte[] ToRGBArrayFast(MappedSubresource mapSource) + { + byte[] bytes = new byte[CaptureWidth * 3 * CaptureHeight]; + int byteIndex = 0; + unsafe + { + byte* ptr = (byte*)mapSource.DataPointer; + byte* rowptr = ptr; + for ( int y = 0; y < CaptureHeight; y++ ) + { + byte* pixelptr = rowptr; + for ( int x = 0; x < CaptureWidth; x++ ) + { + for (int comp = 0; comp < 3; comp++ ) + { + byte lo = *pixelptr++; + byte hi = *pixelptr++; + + // No idea why these values range from 4.6 to 0 instead of 1 to 0 + // f(x) = sqrt(x/5) seems to approximate what the values should be. + bytes[byteIndex++] = (byte)(MathExt.Clamp(Math.Sqrt(Parse16BitFloat2(hi, lo)/4.6), 0, 1) * 255); + } + pixelptr += 2; //skip alpha + } + rowptr += mapSource.RowPitch; + } + } + return bytes; + } + + private byte[] ToRGBArray(MappedSubresource mapSource) { var sourcePtr = mapSource.DataPointer; byte[] bytes = new byte[CaptureWidth * 3 * CaptureHeight]; int byteIndex = 0; +#if true + for ( int y = 0; y < CaptureHeight; y++ ) + { + UInt64[] rowData = new UInt64[CaptureWidth]; + MemCopy(sourcePtr, rowData, (uint)CaptureWidth); + + foreach (Int64 pixelData in rowData) + { + //Func Convert16bitTo8bit = (byteArray, index) => (byte)(((float)BitConverter.ToUInt16(byteArray, index) / (float)UInt16.MaxValue) * 255); + Func Convert16bitTo8bit = (byteArray, index, scale) => + { + //float raw = Parse16BitFloat(byteArray[index + 1], byteArray[index]); + //float raw = Ieee11073ToSingle(byteArray[index + 1], byteArray[index]); + float raw = Parse16BitFloat2(byteArray[index + 1], byteArray[index]); + //double raw2 = raw <= 0.0031308 ? raw * 12.92 : 1.055 * Math.Pow(raw, 1.0f / 2.4f) - 0.055f; + //double final = MathExt.Clamp(raw2, 0.0f, 1.0f); + double final = MathExt.Clamp(raw, 0.0f, 1.0f); + return (byte)(final * 255); + }; + byte[] values = BitConverter.GetBytes(pixelData); + if ( BitConverter.IsLittleEndian ) + { + float alpha = Parse16BitFloat2(values[7], values[6]); + float r = Parse16BitFloat2(values[1], values[0]); + float g = Parse16BitFloat2(values[3], values[2]); + float b = Parse16BitFloat2(values[5], values[4]); + // Byte order : rgba + bytes[byteIndex++] = Convert16bitTo8bit(values, 0, alpha); + bytes[byteIndex++] = Convert16bitTo8bit(values, 2, alpha); + bytes[byteIndex++] = Convert16bitTo8bit(values, 4, alpha); + } + else + { + float alpha = Parse16BitFloat(values[0], values[1]); + // Byte order : abgr + bytes[byteIndex++] = Convert16bitTo8bit(values, 6, alpha); + bytes[byteIndex++] = Convert16bitTo8bit(values, 4, alpha); + bytes[byteIndex++] = Convert16bitTo8bit(values, 2, alpha); + } + } + + sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); + } +#else for ( int y = 0; y < CaptureHeight; y++ ) { Int32[] rowData = new Int32[CaptureWidth]; @@ -271,6 +425,8 @@ private byte[] ToRGBArray(DataBox mapSource) sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); } +#endif + return bytes; } @@ -286,7 +442,7 @@ public void DelayNextCapture() public void Dispose() { _duplicatedOutput?.Dispose(); - _output1?.Dispose(); + _output5?.Dispose(); _output?.Dispose(); _stagingTexture?.Dispose(); _smallerTexture?.Dispose(); diff --git a/HyperionScreenCap/Helper/HyperionTask.cs b/HyperionScreenCap/Helper/HyperionTask.cs index d7780fd..9a8d9e7 100644 --- a/HyperionScreenCap/Helper/HyperionTask.cs +++ b/HyperionScreenCap/Helper/HyperionTask.cs @@ -139,8 +139,10 @@ private void TransmitNextFrame() try { byte[] imageData = _screenCapture.Capture(); - hyperionClient.SendImageData(imageData, _screenCapture.CaptureWidth, _screenCapture.CaptureHeight); - + if (imageData != null) + { + hyperionClient.SendImageData(imageData, _screenCapture.CaptureWidth, _screenCapture.CaptureHeight); + } // Uncomment the following to enable debugging // MiscUtils.SaveRGBArrayToImageFile(imageData, _screenCapture.CaptureWidth, _screenCapture.CaptureHeight, AppConstants.DEBUG_IMAGE_FILE_NAME); } diff --git a/HyperionScreenCap/HyperionScreenCap.csproj b/HyperionScreenCap/HyperionScreenCap.csproj index b45527b..9126753 100644 --- a/HyperionScreenCap/HyperionScreenCap.csproj +++ b/HyperionScreenCap/HyperionScreenCap.csproj @@ -1,5 +1,10 @@  + + + + + @@ -48,6 +53,7 @@ 7.3 prompt MinimumRecommendedRules.ruleset + true bin\Release\ @@ -57,6 +63,7 @@ 7.3 prompt MinimumRecommendedRules.ruleset + true true @@ -68,6 +75,7 @@ prompt MinimumRecommendedRules.ruleset true + true bin\x86\Release\ @@ -78,6 +86,7 @@ prompt MinimumRecommendedRules.ruleset true + true @@ -107,6 +116,9 @@ ..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + ..\packages\Microsoft.Bcl.HashCode.1.1.1\lib\net461\Microsoft.Bcl.HashCode.dll + ..\packages\Microsoft.CodeAnalysis.Common.3.8.0\lib\netstandard2.0\Microsoft.CodeAnalysis.dll @@ -125,14 +137,11 @@ ..\packages\RestSharp.106.11.7\lib\net452\RestSharp.dll - - ..\packages\SharpDX.4.2.0\lib\net45\SharpDX.dll - - - ..\packages\SharpDX.Direct3D11.4.2.0\lib\net45\SharpDX.Direct3D11.dll + + ..\packages\SharpGen.Runtime.2.0.0-beta.11\lib\net471\SharpGen.Runtime.dll - - ..\packages\SharpDX.DXGI.4.2.0\lib\net45\SharpDX.DXGI.dll + + ..\packages\SharpGen.Runtime.COM.2.0.0-beta.11\lib\net45\SharpGen.Runtime.COM.dll ..\packages\SlimDX.4.0.13.44\lib\NET40\SlimDX.dll @@ -163,8 +172,9 @@ - - ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll @@ -174,8 +184,8 @@ ..\packages\System.Reflection.Metadata.5.0.0\lib\net461\System.Reflection.Metadata.dll - - ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll ..\packages\System.Text.Encoding.CodePages.5.0.0\lib\net461\System.Text.Encoding.CodePages.dll @@ -190,6 +200,21 @@ + + ..\packages\Vortice.Direct3D11.2.2.0\lib\netstandard2.0\Vortice.Direct3D11.dll + + + ..\packages\Vortice.DirectX.2.2.0\lib\netstandard2.0\Vortice.DirectX.dll + + + ..\packages\Vortice.DXGI.2.2.0\lib\netstandard2.0\Vortice.DXGI.dll + + + ..\packages\Vortice.Mathematics.1.4.22\lib\netstandard2.0\Vortice.Mathematics.dll + + + ..\packages\WindowsBase_Core.STW.4.0.30319.1\lib\net40\WindowsBase.dll + @@ -391,6 +416,11 @@ + + + + + diff --git a/HyperionScreenCap/Program.cs b/HyperionScreenCap/Program.cs index bba3c43..ca67c6b 100644 --- a/HyperionScreenCap/Program.cs +++ b/HyperionScreenCap/Program.cs @@ -6,7 +6,9 @@ using System.IO; using System.Linq; using System.Net; +using System.Management; using System.Windows.Forms; +using System.Runtime.InteropServices; namespace HyperionScreenCap { @@ -19,9 +21,62 @@ static Program() { } - [System.Runtime.InteropServices.DllImport("user32.dll")] + [DllImport("user32.dll")] private static extern bool SetProcessDPIAware(); + [DllImport("User32.dll")] + public static extern bool SetProcessDpiAwarenessContext(int dpiFlag); + + [DllImport("SHCore.dll")] + public static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness); + + public enum PROCESS_DPI_AWARENESS + { + Process_DPI_Unaware = 0, + Process_System_DPI_Aware = 1, + Process_Per_Monitor_DPI_Aware = 2 + } + + public enum DPI_AWARENESS_CONTEXT + { + DPI_AWARENESS_CONTEXT_UNAWARE = 16, + DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = 17, + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18, + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34 + } + + + + /// + private static void SetDpiAwareness() + { + var query = "SELECT * FROM Win32_OperatingSystem"; + var searcher = new ManagementObjectSearcher(query); + var info = searcher.Get().Cast().FirstOrDefault(); +// var caption = info.Properties["Caption"].Value.ToString(); + var version = info.Properties["Version"].Value.ToString(); +// var spMajorVersion = info.Properties["ServicePackMajorVersion"].Value.ToString(); +// var spMinorVersion = info.Properties["ServicePackMinorVersion"].Value.ToString(); + Version winVersion = new Version(version); + // Windows 8.1 added support for per monitor DPI + if ( winVersion >= new Version(6, 3, 0)) + { + // Windows 10 creators update added support for per monitor v2 + if ( winVersion >= new Version(10, 0, 15063)) + { + SetProcessDpiAwarenessContext((int)DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } + else + { + SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_Per_Monitor_DPI_Aware); + } + } + else + { + SetProcessDPIAware(); + }; + } + /// /// The main entry point for the application. /// @@ -35,7 +90,7 @@ private static void Main() LOG.Info("**********************************************************"); // Set DPI awareness - SetProcessDPIAware(); + SetDpiAwareness(); // Check if already running and exit if that's the case if (IsProgramRunning("hyperionscreencap", 0) > 1) diff --git a/HyperionScreenCap/Properties/AssemblyInfo.cs b/HyperionScreenCap/Properties/AssemblyInfo.cs index f80e7b0..df27e7f 100644 --- a/HyperionScreenCap/Properties/AssemblyInfo.cs +++ b/HyperionScreenCap/Properties/AssemblyInfo.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Runtime.InteropServices; +using System.Windows.Media; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -33,3 +34,6 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("2.9.0.0")] //[assembly: AssemblyFileVersion("2.0.0.0")] Commented out so that it will be generated automatically + +// Needed for DuplicateOuput1 to work? :/ +[assembly: DisableDpiAwareness] diff --git a/HyperionScreenCap/app.config b/HyperionScreenCap/app.config index 36c3a75..7098433 100644 --- a/HyperionScreenCap/app.config +++ b/HyperionScreenCap/app.config @@ -114,7 +114,7 @@ - + @@ -132,6 +132,14 @@ + + + + + + + + diff --git a/HyperionScreenCap/packages.config b/HyperionScreenCap/packages.config index e3389c4..137ecd9 100644 --- a/HyperionScreenCap/packages.config +++ b/HyperionScreenCap/packages.config @@ -9,6 +9,7 @@ + @@ -16,9 +17,8 @@ - - - + + @@ -28,10 +28,15 @@ - + - + + + + + + \ No newline at end of file From d95e29465a4736188aa80402c266fba9f69a7dd5 Mon Sep 17 00:00:00 2001 From: Kris Taeleman Date: Thu, 12 Jan 2023 13:28:53 -0800 Subject: [PATCH 2/8] Add texture format detection and cleanup --- .../Capture/Dx11ScreenCapture.cs | 235 +++++++----------- 1 file changed, 86 insertions(+), 149 deletions(-) diff --git a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs index 33373fc..7983f74 100644 --- a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs +++ b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs @@ -101,25 +101,13 @@ public DX11ScreenCapture(int adapterIndex, int monitorIndex, int scalingFactor, public void Initialize() { - int mipLevels; - if ( _scalingFactor == 1 ) - mipLevels = 1; - else if ( _scalingFactor > 0 && _scalingFactor % 2 == 0 ) - { - /// Mip level for a scaling factor other than one is computed as follows: - /// 2^n = 2 + n - 1 where LHS is the scaling factor and RHS is the MipLevels value. - _scalingFactorLog2 = Convert.ToInt32(Math.Log(_scalingFactor, 2)); - mipLevels = 2 + _scalingFactorLog2 - 1; - } - else - throw new Exception("Invalid scaling factor. Allowed valued are 1, 2, 4, etc."); // Create DXGI IDXGIFactory2 DXGI.CreateDXGIFactory2(false,out _factory); _factory.EnumAdapters1(_adapterIndex, out _adapter); // Create device from Adapter - D3D11CreateDevice(_adapter, DriverType.Unknown, /*DeviceCreationFlags.BgraSupport*/ DeviceCreationFlags.None, s_featureLevels, out _device); + D3D11CreateDevice(_adapter, DriverType.Unknown, DeviceCreationFlags.BgraSupport, s_featureLevels, out _device); // Get DXGI.Output _adapter.EnumOutputs(_monitorIndex, out _output); @@ -132,13 +120,48 @@ public void Initialize() CaptureWidth = _width / _scalingFactor; CaptureHeight = _height / _scalingFactor; + + // Initialize duplicator so we can see what the output format is + InitDesktopDuplicator(); + + + _minCaptureTime = 1000 / _maxFps; + _captureTimer = new Stopwatch(); + _disposed = false; + } + + private void InitDesktopDuplicator() + { + // We're potentially reinitializing the duplicator, which could change output format + // So make sure we reinitialize our textures + _stagingTexture?.Dispose(); + _smallerTexture?.Dispose(); + _smallerTextureView?.Dispose(); + + // Duplicate the output + Format[] DesktopFormats = { Format.R16G16B16A16_Float, Format.B8G8R8A8_UNorm }; + _duplicatedOutput = _output5.DuplicateOutput1(_device, DesktopFormats); + + // Calculate miplevels + int mipLevels; + if ( _scalingFactor == 1 ) + mipLevels = 1; + else if ( _scalingFactor > 0 && _scalingFactor % 2 == 0 ) + { + /// Mip level for a scaling factor other than one is computed as follows: + /// 2^n = 2 + n - 1 where LHS is the scaling factor and RHS is the MipLevels value. + _scalingFactorLog2 = Convert.ToInt32(Math.Log(_scalingFactor, 2)); + mipLevels = 2 + _scalingFactorLog2 - 1; + } + else + throw new Exception("Invalid scaling factor. Allowed valued are 1, 2, 4, etc."); // Create Staging texture CPU-accessible var stagingTextureDesc = new Texture2DDescription { CPUAccessFlags = CpuAccessFlags.Read, BindFlags = BindFlags.None, - Format = Format.R16G16B16A16_Float, + Format = _duplicatedOutput.Description.ModeDescription.Format, Width = CaptureWidth, Height = CaptureHeight, MiscFlags = ResourceOptionFlags.None, @@ -154,7 +177,7 @@ public void Initialize() { CPUAccessFlags = CpuAccessFlags.None, BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, - Format = Format.R16G16B16A16_Float, + Format = _duplicatedOutput.Description.ModeDescription.Format, Width = _width, Height = _height, MiscFlags = ResourceOptionFlags.GenerateMips, @@ -166,19 +189,6 @@ public void Initialize() _smallerTexture = _device.CreateTexture2D(smallerTextureDesc); _smallerTextureView = _device.CreateShaderResourceView(_smallerTexture); - _minCaptureTime = 1000 / _maxFps; - _captureTimer = new Stopwatch(); - _disposed = false; - - InitDesktopDuplicator(); - } - - private void InitDesktopDuplicator() - { - // Duplicate the output - Format[] DesktopFormats = { Format.R16G16B16A16_Float, Format.B8G8R8A8_UNorm }; - _duplicatedOutput = _output5.DuplicateOutput1(_device, DesktopFormats); - _desktopDuplicatorInvalid = false; } @@ -242,7 +252,7 @@ private byte[] ManagedCapture() // Get the desktop capture texture MappedSubresource mapSource = _device.ImmediateContext.Map(_stagingTexture, 0, MapMode.Read); - _lastCapturedFrame = ToRGBArrayFast(mapSource); + _lastCapturedFrame = ToRGBArray(mapSource, _stagingTexture.Description.Format); return _lastCapturedFrame; } finally @@ -255,39 +265,7 @@ private byte[] ManagedCapture() } } - [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)] - static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length); - - public static unsafe void MemCopy(IntPtr ptrSource, ulong[] dest, uint elements) - { - fixed (ulong* ptrDest = &dest[0]) - { - CopyMemory((IntPtr)ptrDest, ptrSource, elements * 8); // 8 bytes per element - } - } - public static float Parse16BitFloat(byte HI, byte LO) - { - // Program assumes ints are at least 16 bits - int fullFloat = ((HI << 8) | LO); - int exponent = (HI & 0b01111110) >> 1; // minor optimisation can be placed here - int mant = fullFloat & 0x01FF; - - // Special values - if (exponent == 0b00111111) // If using constants, shift right by 1 - { - // Check for non or inf - return mant != 0 ? float.NaN : - ((HI & 0x80) == 0 ? float.PositiveInfinity : float.NegativeInfinity); - } - else // normal/denormal values: pad numbers - { - exponent = exponent - 31 + 127; - mant = mant << 14; - Int32 finalFloat = (HI & 0x80) << 24 | (exponent << 23) | mant; - return BitConverter.ToSingle(BitConverter.GetBytes(finalFloat), 0); - } - } - public static float Parse16BitFloat2(byte Hi, byte Lo) + public static float Parse16BitFloat(byte Hi, byte Lo) { int fullFloat = ((Hi << 8) | Lo); int mant = fullFloat & 0x03ff; // 10 bits mantissa @@ -320,113 +298,72 @@ public static float Parse16BitFloat2(byte Hi, byte Lo) /// /// /// - private byte[] ToRGBArrayFast(MappedSubresource mapSource) + private byte[] ToRGBArray(MappedSubresource mapSource, Format format) { byte[] bytes = new byte[CaptureWidth * 3 * CaptureHeight]; int byteIndex = 0; - unsafe + + if (format == Format.R16G16B16A16_Float) { - byte* ptr = (byte*)mapSource.DataPointer; - byte* rowptr = ptr; - for ( int y = 0; y < CaptureHeight; y++ ) + unsafe { - byte* pixelptr = rowptr; - for ( int x = 0; x < CaptureWidth; x++ ) + byte* ptr = (byte*)mapSource.DataPointer; + byte* rowptr = ptr; + for (int y = 0; y < CaptureHeight; y++) { - for (int comp = 0; comp < 3; comp++ ) + byte* pixelptr = rowptr; + for (int x = 0; x < CaptureWidth; x++) { - byte lo = *pixelptr++; - byte hi = *pixelptr++; - - // No idea why these values range from 4.6 to 0 instead of 1 to 0 - // f(x) = sqrt(x/5) seems to approximate what the values should be. - bytes[byteIndex++] = (byte)(MathExt.Clamp(Math.Sqrt(Parse16BitFloat2(hi, lo)/4.6), 0, 1) * 255); + for (int comp = 0; comp < 3; comp++) + { + byte lo = *pixelptr++; + byte hi = *pixelptr++; + + // No idea why these values range from 4.6 to 0 instead of 1 to 0 + // f(x) = sqrt(x/4.6) seems to approximate what the values should be. + bytes[byteIndex++] = (byte)(MathExt.Clamp(Math.Sqrt(Parse16BitFloat(hi, lo) / 4.6), 0, 1) * 255); + } + pixelptr += 2; //skip alpha } - pixelptr += 2; //skip alpha + rowptr += mapSource.RowPitch; } - rowptr += mapSource.RowPitch; } } - return bytes; - } - - private byte[] ToRGBArray(MappedSubresource mapSource) - { - var sourcePtr = mapSource.DataPointer; - byte[] bytes = new byte[CaptureWidth * 3 * CaptureHeight]; - int byteIndex = 0; -#if true - for ( int y = 0; y < CaptureHeight; y++ ) + else if (format == Format.B8G8R8A8_UNorm) { - UInt64[] rowData = new UInt64[CaptureWidth]; - MemCopy(sourcePtr, rowData, (uint)CaptureWidth); + IntPtr sourcePtr = mapSource.DataPointer; - foreach (Int64 pixelData in rowData) + for ( int y = 0; y < CaptureHeight; y++ ) { - //Func Convert16bitTo8bit = (byteArray, index) => (byte)(((float)BitConverter.ToUInt16(byteArray, index) / (float)UInt16.MaxValue) * 255); - Func Convert16bitTo8bit = (byteArray, index, scale) => - { - //float raw = Parse16BitFloat(byteArray[index + 1], byteArray[index]); - //float raw = Ieee11073ToSingle(byteArray[index + 1], byteArray[index]); - float raw = Parse16BitFloat2(byteArray[index + 1], byteArray[index]); - //double raw2 = raw <= 0.0031308 ? raw * 12.92 : 1.055 * Math.Pow(raw, 1.0f / 2.4f) - 0.055f; - //double final = MathExt.Clamp(raw2, 0.0f, 1.0f); - double final = MathExt.Clamp(raw, 0.0f, 1.0f); - return (byte)(final * 255); - }; - byte[] values = BitConverter.GetBytes(pixelData); - if ( BitConverter.IsLittleEndian ) - { - float alpha = Parse16BitFloat2(values[7], values[6]); - float r = Parse16BitFloat2(values[1], values[0]); - float g = Parse16BitFloat2(values[3], values[2]); - float b = Parse16BitFloat2(values[5], values[4]); - // Byte order : rgba - bytes[byteIndex++] = Convert16bitTo8bit(values, 0, alpha); - bytes[byteIndex++] = Convert16bitTo8bit(values, 2, alpha); - bytes[byteIndex++] = Convert16bitTo8bit(values, 4, alpha); - } - else + Int32[] rowData = new Int32[CaptureWidth]; + Marshal.Copy(sourcePtr, rowData, 0, CaptureWidth); + + foreach ( Int32 pixelData in rowData ) { - float alpha = Parse16BitFloat(values[0], values[1]); - // Byte order : abgr - bytes[byteIndex++] = Convert16bitTo8bit(values, 6, alpha); - bytes[byteIndex++] = Convert16bitTo8bit(values, 4, alpha); - bytes[byteIndex++] = Convert16bitTo8bit(values, 2, alpha); + byte[] values = BitConverter.GetBytes(pixelData); + if ( BitConverter.IsLittleEndian ) + { + // Byte order : bgra + bytes[byteIndex++] = values[2]; + bytes[byteIndex++] = values[1]; + bytes[byteIndex++] = values[0]; + } + else + { + // Byte order : argb + bytes[byteIndex++] = values[1]; + bytes[byteIndex++] = values[2]; + bytes[byteIndex++] = values[3]; + } } - } - sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); + sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); + } } -#else - for ( int y = 0; y < CaptureHeight; y++ ) + else { - Int32[] rowData = new Int32[CaptureWidth]; - Marshal.Copy(sourcePtr, rowData, 0, CaptureWidth); - - foreach ( Int32 pixelData in rowData ) - { - byte[] values = BitConverter.GetBytes(pixelData); - if ( BitConverter.IsLittleEndian ) - { - // Byte order : bgra - bytes[byteIndex++] = values[2]; - bytes[byteIndex++] = values[1]; - bytes[byteIndex++] = values[0]; - } - else - { - // Byte order : argb - bytes[byteIndex++] = values[1]; - bytes[byteIndex++] = values[2]; - bytes[byteIndex++] = values[3]; - } - } - - sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); + throw new NotImplementedException($"Texture format {format.ToString()} is not supported."); } -#endif - return bytes; } From a26cb79f8bf2340291fca6f214717203c2b14090 Mon Sep 17 00:00:00 2001 From: Kris Taeleman Date: Thu, 12 Jan 2023 16:03:04 -0800 Subject: [PATCH 3/8] Go back to Vortice as it isn't needed --- .../Capture/Dx11ScreenCapture.cs | 122 +++++++++--------- HyperionScreenCap/HyperionScreenCap.csproj | 51 ++------ HyperionScreenCap/app.config | 2 +- HyperionScreenCap/packages.config | 13 +- 4 files changed, 83 insertions(+), 105 deletions(-) diff --git a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs index 7983f74..f1416b4 100644 --- a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs +++ b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs @@ -1,8 +1,7 @@ using HyperionScreenCap.Capture; -using Vortice; -using Vortice.Direct3D; -using Vortice.Direct3D11; -using Vortice.DXGI; +using SharpDX; +using SharpDX.Direct3D11; +using SharpDX.DXGI; using System; using System.Collections.Generic; using System.Diagnostics; @@ -13,8 +12,6 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; -using static Vortice.Direct3D11.D3D11; -using static Vortice.DXGI.DXGI; @@ -39,15 +36,24 @@ class DX11ScreenCapture : IScreenCapture private int _maxFps; private int _frameCaptureTimeout; - private IDXGIFactory2 _factory; - private IDXGIAdapter1 _adapter; - private IDXGIOutput _output; - private IDXGIOutput5 _output5; - private ID3D11Device _device; - private ID3D11Texture2D _stagingTexture; - private ID3D11Texture2D _smallerTexture; - private ID3D11ShaderResourceView _smallerTextureView; - private IDXGIOutputDuplication _duplicatedOutput; + private Factory1 _factory; + private Adapter _adapter; + private Output _output; + private Output5 _output5; + private SharpDX.Direct3D11.Device _device; + private Texture2D _stagingTexture; + private Texture2D _smallerTexture; + private ShaderResourceView _smallerTextureView; + private OutputDuplication _duplicatedOutput; + //private IDXGIFactory2 _factory; + //private IDXGIAdapter1 _adapter; + //private IDXGIOutput _output; + //private IDXGIOutput5 _output5; + //private ID3D11Device _device; + //private ID3D11Texture2D _stagingTexture; + //private ID3D11Texture2D _smallerTexture; + //private ID3D11ShaderResourceView _smallerTextureView; + //private IDXGIOutputDuplication _duplicatedOutput; private int _scalingFactorLog2; private int _width; private int _height; @@ -56,10 +62,10 @@ class DX11ScreenCapture : IScreenCapture private Stopwatch _captureTimer; private bool _desktopDuplicatorInvalid; private bool _disposed; - private static readonly FeatureLevel[] s_featureLevels = new[] - { - FeatureLevel.Level_11_0 - }; + //private static readonly FeatureLevel[] s_featureLevels = new[] + //{ + //FeatureLevel.Level_11_0 + //}; public int CaptureWidth { get; private set; } public int CaptureHeight { get; private set; } @@ -67,25 +73,22 @@ class DX11ScreenCapture : IScreenCapture public static String GetAvailableMonitors() { StringBuilder response = new StringBuilder(); - IDXGIFactory2 tempFactory = null; - DXGI.CreateDXGIFactory2(false,out tempFactory); - //using ( IDXGIFactory2 factory = new IDXGIFactory2() ) + using ( Factory1 factory = new Factory1() ) { int adapterIndex = 0; - while(tempFactory.EnumAdapters(adapterIndex, out IDXGIAdapter adapter) == SharpGen.Runtime.Result.Ok) + foreach(Adapter adapter in factory.Adapters) { response.Append($"Adapter Index {adapterIndex++}: {adapter.Description.Description}\n"); int outputIndex = 0; - while(adapter.EnumOutputs(outputIndex, out IDXGIOutput output) == SharpGen.Runtime.Result.Ok) + foreach(Output output in adapter.Outputs) { response.Append($"\tMonitor Index {outputIndex++}: {output.Description.DeviceName}"); - var desktopBounds = output.Description.DesktopCoordinates; + var desktopBounds = output.Description.DesktopBounds; response.Append($" {desktopBounds.Right - desktopBounds.Left}×{desktopBounds.Bottom - desktopBounds.Top}\n"); } response.Append("\n"); } } - tempFactory.Dispose(); return response.ToString(); } @@ -102,19 +105,19 @@ public DX11ScreenCapture(int adapterIndex, int monitorIndex, int scalingFactor, public void Initialize() { - // Create DXGI IDXGIFactory2 - DXGI.CreateDXGIFactory2(false,out _factory); - _factory.EnumAdapters1(_adapterIndex, out _adapter); + // Create DXGI Factory1 + _factory = new Factory1(); + _adapter = _factory.GetAdapter1(_adapterIndex); // Create device from Adapter - D3D11CreateDevice(_adapter, DriverType.Unknown, DeviceCreationFlags.BgraSupport, s_featureLevels, out _device); + _device = new SharpDX.Direct3D11.Device(_adapter); // Get DXGI.Output - _adapter.EnumOutputs(_monitorIndex, out _output); - _output5 = _output.QueryInterface(); + _output = _adapter.GetOutput(_monitorIndex); + _output5 = _output.QueryInterface(); // Width/Height of desktop to capture - var desktopBounds = _output.Description.DesktopCoordinates; + var desktopBounds = _output.Description.DesktopBounds; _width = desktopBounds.Right - desktopBounds.Left; _height = desktopBounds.Bottom - desktopBounds.Top; @@ -140,7 +143,7 @@ private void InitDesktopDuplicator() // Duplicate the output Format[] DesktopFormats = { Format.R16G16B16A16_Float, Format.B8G8R8A8_UNorm }; - _duplicatedOutput = _output5.DuplicateOutput1(_device, DesktopFormats); + _duplicatedOutput = _output5.DuplicateOutput1(_device, 0, DesktopFormats.Count(), DesktopFormats); // Calculate miplevels int mipLevels; @@ -159,35 +162,35 @@ private void InitDesktopDuplicator() // Create Staging texture CPU-accessible var stagingTextureDesc = new Texture2DDescription { - CPUAccessFlags = CpuAccessFlags.Read, + CpuAccessFlags = CpuAccessFlags.Read, BindFlags = BindFlags.None, Format = _duplicatedOutput.Description.ModeDescription.Format, Width = CaptureWidth, Height = CaptureHeight, - MiscFlags = ResourceOptionFlags.None, + OptionFlags = ResourceOptionFlags.None, MipLevels = 1, ArraySize = 1, SampleDescription = { Count = 1, Quality = 0 }, Usage = ResourceUsage.Staging }; - _stagingTexture = _device.CreateTexture2D(stagingTextureDesc); + _stagingTexture = new Texture2D(_device, stagingTextureDesc); // Create smaller texture to downscale the captured image var smallerTextureDesc = new Texture2DDescription { - CPUAccessFlags = CpuAccessFlags.None, + CpuAccessFlags = CpuAccessFlags.None, BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, Format = _duplicatedOutput.Description.ModeDescription.Format, Width = _width, Height = _height, - MiscFlags = ResourceOptionFlags.GenerateMips, + OptionFlags = ResourceOptionFlags.GenerateMipMaps, MipLevels = mipLevels, ArraySize = 1, SampleDescription = { Count = 1, Quality = 0 }, Usage = ResourceUsage.Default }; - _smallerTexture = _device.CreateTexture2D(smallerTextureDesc); - _smallerTextureView = _device.CreateShaderResourceView(_smallerTexture); + _smallerTexture = new Texture2D(_device, smallerTextureDesc); + _smallerTextureView = new ShaderResourceView(_device, _smallerTexture); _desktopDuplicatorInvalid = false; } @@ -209,49 +212,52 @@ public byte[] Capture() private byte[] ManagedCapture() { - IDXGIResource screenResource = null; - OutduplFrameInfo duplicateFrameInformation; + SharpDX.DXGI.Resource screenResource = null; + OutputDuplicateFrameInformation duplicateFrameInformation; try { + try { // Try to get duplicated frame within given time - SharpGen.Runtime.Result res = _duplicatedOutput.AcquireNextFrame(_frameCaptureTimeout, out duplicateFrameInformation, out screenResource); + _duplicatedOutput.AcquireNextFrame(_frameCaptureTimeout, out duplicateFrameInformation, out screenResource); - if ( res == SharpGen.Runtime.Result.WaitTimeout.Code && _lastCapturedFrame != null ) + if ( duplicateFrameInformation.LastPresentTime == 0 && _lastCapturedFrame != null ) + return _lastCapturedFrame; + } + catch ( SharpDXException ex ) + { + if ( ex.ResultCode.Code == SharpDX.DXGI.ResultCode.WaitTimeout.Code && _lastCapturedFrame != null ) return _lastCapturedFrame; - if (res == new SharpGen.Runtime.Result(0x887A0026)) // ACCESS_LOST - { + if ( ex.ResultCode.Code == SharpDX.DXGI.ResultCode.AccessLost.Code ) _desktopDuplicatorInvalid = true; - return null; - } - if ( duplicateFrameInformation.LastPresentTime == 0 && _lastCapturedFrame != null ) - return _lastCapturedFrame; + + throw ex; } // Check if scaling is used if ( CaptureWidth != _width ) { // Copy resource into memory that can be accessed by the CPU - using ( var screenTexture2D = screenResource.QueryInterface() ) - _device.ImmediateContext.CopySubresourceRegion(_smallerTexture, 0, 0, 0, 0, screenTexture2D, 0); + using ( var screenTexture2D = screenResource.QueryInterface() ) + _device.ImmediateContext.CopySubresourceRegion(screenTexture2D, 0, null, _smallerTexture, 0); // Generates the mipmap of the screen _device.ImmediateContext.GenerateMips(_smallerTextureView); // Copy the mipmap of smallerTexture (size/ scalingFactor) to the staging texture: 1 for /2, 2 for /4...etc - _device.ImmediateContext.CopySubresourceRegion(_stagingTexture, 0, 0, 0, 0, _smallerTexture, _scalingFactorLog2); + _device.ImmediateContext.CopySubresourceRegion(_smallerTexture, _scalingFactorLog2, null, _stagingTexture, 0); } else { // Copy resource into memory that can be accessed by the CPU - using ( var screenTexture2D = screenResource.QueryInterface() ) + using ( var screenTexture2D = screenResource.QueryInterface() ) _device.ImmediateContext.CopyResource(screenTexture2D, _stagingTexture); } // Get the desktop capture texture - MappedSubresource mapSource = _device.ImmediateContext.Map(_stagingTexture, 0, MapMode.Read); + var mapSource = _device.ImmediateContext.MapSubresource(_stagingTexture, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None); _lastCapturedFrame = ToRGBArray(mapSource, _stagingTexture.Description.Format); return _lastCapturedFrame; } @@ -259,7 +265,7 @@ private byte[] ManagedCapture() { screenResource?.Dispose(); // Fixed OUT_OF_MEMORY issue on AMD Radeon cards. Ignoring all exceptions during unmapping. - try { _device.ImmediateContext.Unmap(_stagingTexture, 0); } catch { }; + try { _device.ImmediateContext.UnmapSubresource(_stagingTexture, 0); } catch { }; // Ignore DXGI_ERROR_INVALID_CALL, DXGI_ERROR_ACCESS_LOST errors since capture is already complete try { _duplicatedOutput.ReleaseFrame(); } catch { } } @@ -298,7 +304,7 @@ public static float Parse16BitFloat(byte Hi, byte Lo) /// /// /// - private byte[] ToRGBArray(MappedSubresource mapSource, Format format) + private byte[] ToRGBArray(DataBox mapSource, Format format) { byte[] bytes = new byte[CaptureWidth * 3 * CaptureHeight]; int byteIndex = 0; diff --git a/HyperionScreenCap/HyperionScreenCap.csproj b/HyperionScreenCap/HyperionScreenCap.csproj index 9126753..cf11d6c 100644 --- a/HyperionScreenCap/HyperionScreenCap.csproj +++ b/HyperionScreenCap/HyperionScreenCap.csproj @@ -1,10 +1,5 @@  - - - - - @@ -53,7 +48,6 @@ 7.3 prompt MinimumRecommendedRules.ruleset - true bin\Release\ @@ -63,7 +57,6 @@ 7.3 prompt MinimumRecommendedRules.ruleset - true true @@ -75,7 +68,6 @@ prompt MinimumRecommendedRules.ruleset true - true bin\x86\Release\ @@ -116,9 +108,6 @@ ..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll - - ..\packages\Microsoft.Bcl.HashCode.1.1.1\lib\net461\Microsoft.Bcl.HashCode.dll - ..\packages\Microsoft.CodeAnalysis.Common.3.8.0\lib\netstandard2.0\Microsoft.CodeAnalysis.dll @@ -134,14 +123,18 @@ ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + ..\packages\RestSharp.106.11.7\lib\net452\RestSharp.dll - - ..\packages\SharpGen.Runtime.2.0.0-beta.11\lib\net471\SharpGen.Runtime.dll + + ..\packages\SharpDX.4.2.0\lib\net45\SharpDX.dll + + + ..\packages\SharpDX.Direct3D11.4.2.0\lib\net45\SharpDX.Direct3D11.dll - - ..\packages\SharpGen.Runtime.COM.2.0.0-beta.11\lib\net45\SharpGen.Runtime.COM.dll + + ..\packages\SharpDX.DXGI.4.2.0\lib\net45\SharpDX.DXGI.dll ..\packages\SlimDX.4.0.13.44\lib\NET40\SlimDX.dll @@ -184,8 +177,8 @@ ..\packages\System.Reflection.Metadata.5.0.0\lib\net461\System.Reflection.Metadata.dll - - ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll ..\packages\System.Text.Encoding.CodePages.5.0.0\lib\net461\System.Text.Encoding.CodePages.dll @@ -200,21 +193,7 @@ - - ..\packages\Vortice.Direct3D11.2.2.0\lib\netstandard2.0\Vortice.Direct3D11.dll - - - ..\packages\Vortice.DirectX.2.2.0\lib\netstandard2.0\Vortice.DirectX.dll - - - ..\packages\Vortice.DXGI.2.2.0\lib\netstandard2.0\Vortice.DXGI.dll - - - ..\packages\Vortice.Mathematics.1.4.22\lib\netstandard2.0\Vortice.Mathematics.dll - - - ..\packages\WindowsBase_Core.STW.4.0.30319.1\lib\net40\WindowsBase.dll - + @@ -407,6 +386,9 @@ + + + @@ -416,11 +398,6 @@ - - - - - diff --git a/HyperionScreenCap/app.config b/HyperionScreenCap/app.config index 7098433..4170a72 100644 --- a/HyperionScreenCap/app.config +++ b/HyperionScreenCap/app.config @@ -114,7 +114,7 @@ - + diff --git a/HyperionScreenCap/packages.config b/HyperionScreenCap/packages.config index 137ecd9..48bf75b 100644 --- a/HyperionScreenCap/packages.config +++ b/HyperionScreenCap/packages.config @@ -9,7 +9,6 @@ - @@ -17,8 +16,9 @@ - - + + + @@ -31,12 +31,7 @@ - + - - - - - \ No newline at end of file From 70c22d360015b77bf1b044c0696a6164946ef38b Mon Sep 17 00:00:00 2001 From: Kris Taeleman Date: Thu, 12 Jan 2023 16:16:27 -0800 Subject: [PATCH 4/8] Some extra comments --- HyperionScreenCap/Capture/Dx11ScreenCapture.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs index f1416b4..086ead3 100644 --- a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs +++ b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs @@ -273,6 +273,8 @@ private byte[] ManagedCapture() public static float Parse16BitFloat(byte Hi, byte Lo) { + // From https://stackoverflow.com/questions/6162651/half-precision-floating-point-in-java/6162687#6162687 + int fullFloat = ((Hi << 8) | Lo); int mant = fullFloat & 0x03ff; // 10 bits mantissa int exp = fullFloat & 0x7c00; // 5 bits exponent From 74951e962069aad76190f6b887a1cea35dbb0b7e Mon Sep 17 00:00:00 2001 From: Kris Taeleman Date: Thu, 12 Jan 2023 16:25:48 -0800 Subject: [PATCH 5/8] Some extra comments --- HyperionScreenCap/Program.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/HyperionScreenCap/Program.cs b/HyperionScreenCap/Program.cs index ca67c6b..160d1e5 100644 --- a/HyperionScreenCap/Program.cs +++ b/HyperionScreenCap/Program.cs @@ -50,13 +50,12 @@ public enum DPI_AWARENESS_CONTEXT /// private static void SetDpiAwareness() { + // Get windows version to set the correct dpi awareness context + // This is needed for DXGI 1.5 OutputDuplicate1 to work var query = "SELECT * FROM Win32_OperatingSystem"; var searcher = new ManagementObjectSearcher(query); var info = searcher.Get().Cast().FirstOrDefault(); -// var caption = info.Properties["Caption"].Value.ToString(); var version = info.Properties["Version"].Value.ToString(); -// var spMajorVersion = info.Properties["ServicePackMajorVersion"].Value.ToString(); -// var spMinorVersion = info.Properties["ServicePackMinorVersion"].Value.ToString(); Version winVersion = new Version(version); // Windows 8.1 added support for per monitor DPI if ( winVersion >= new Version(6, 3, 0)) From fa3051b23011026f7dabbd727c5ae4cc6749c167 Mon Sep 17 00:00:00 2001 From: Kris Taeleman Date: Tue, 7 Mar 2023 13:23:32 -0800 Subject: [PATCH 6/8] Add support for R10G10B10A2_UNorm --- .../Capture/Dx11ScreenCapture.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs index 086ead3..91b0eed 100644 --- a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs +++ b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs @@ -368,6 +368,43 @@ private byte[] ToRGBArray(DataBox mapSource, Format format) sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); } } + else if (format == Format.R10G10B10A2_UNorm) + { + IntPtr sourcePtr = mapSource.DataPointer; + + for ( int y = 0; y < CaptureHeight; y++ ) + { + Int32[] rowData = new Int32[CaptureWidth]; + Marshal.Copy(sourcePtr, rowData, 0, CaptureWidth); + + // From http://threadlocalmutex.com/?page_id=60, 10bit to 8bit conversion: (x * 1021 + 2048) >> 12 + Func convert10bitTo8bit = (x) => (byte)MathExt.Clamp((x * 1021 + 2048) >> 12, 0, 255); + + foreach ( Int32 pixelData in rowData ) + { + Int32 r = pixelData & 0x3FF; + Int32 b = (pixelData >> 10) & 0x3FF; + Int32 g = (pixelData >> 20) & 0x3FF; + + if ( BitConverter.IsLittleEndian ) + { + // Byte order : bgra + bytes[byteIndex++] = convert10bitTo8bit(b); + bytes[byteIndex++] = convert10bitTo8bit(g); + bytes[byteIndex++] = convert10bitTo8bit(r); + } + else + { + // Byte order : argb + bytes[byteIndex++] = convert10bitTo8bit(r); + bytes[byteIndex++] = convert10bitTo8bit(g); + bytes[byteIndex++] = convert10bitTo8bit(b); + } + } + + sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); + } + } else { throw new NotImplementedException($"Texture format {format.ToString()} is not supported."); From 534012bc7cc325d9e119743fccbd06e2e226db08 Mon Sep 17 00:00:00 2001 From: Kris Taeleman Date: Tue, 7 Mar 2023 13:58:14 -0800 Subject: [PATCH 7/8] Add R10G10B10A2 to dxgi desktopformats --- HyperionScreenCap/Capture/Dx11ScreenCapture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs index 91b0eed..8497a36 100644 --- a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs +++ b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs @@ -142,7 +142,7 @@ private void InitDesktopDuplicator() _smallerTextureView?.Dispose(); // Duplicate the output - Format[] DesktopFormats = { Format.R16G16B16A16_Float, Format.B8G8R8A8_UNorm }; + Format[] DesktopFormats = { Format.R16G16B16A16_Float, Format.B8G8R8A8_UNorm, Format.R10G10B10A2_UNorm}; _duplicatedOutput = _output5.DuplicateOutput1(_device, 0, DesktopFormats.Count(), DesktopFormats); // Calculate miplevels From 38775af16590968c5fdfb91ac544ba9e9bbd951b Mon Sep 17 00:00:00 2001 From: Kris Taeleman Date: Wed, 8 Mar 2023 09:21:13 -0800 Subject: [PATCH 8/8] Fix for color swap with R10G10B10A2 and remove endian check as our target is always Windows here. --- .../Capture/Dx11ScreenCapture.cs | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs index 8497a36..4207d81 100644 --- a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs +++ b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs @@ -383,23 +383,12 @@ private byte[] ToRGBArray(DataBox mapSource, Format format) foreach ( Int32 pixelData in rowData ) { Int32 r = pixelData & 0x3FF; - Int32 b = (pixelData >> 10) & 0x3FF; - Int32 g = (pixelData >> 20) & 0x3FF; + Int32 g = (pixelData >> 10) & 0x3FF; + Int32 b = (pixelData >> 20) & 0x3FF; - if ( BitConverter.IsLittleEndian ) - { - // Byte order : bgra - bytes[byteIndex++] = convert10bitTo8bit(b); - bytes[byteIndex++] = convert10bitTo8bit(g); - bytes[byteIndex++] = convert10bitTo8bit(r); - } - else - { - // Byte order : argb - bytes[byteIndex++] = convert10bitTo8bit(r); - bytes[byteIndex++] = convert10bitTo8bit(g); - bytes[byteIndex++] = convert10bitTo8bit(b); - } + bytes[byteIndex++] = convert10bitTo8bit(r); + bytes[byteIndex++] = convert10bitTo8bit(g); + bytes[byteIndex++] = convert10bitTo8bit(b); } sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch);