WIP (not working!) First draft for generic color format handling

This commit is contained in:
Darth Affe 2023-02-22 23:53:47 +01:00
parent af401448e7
commit 2cc8a9b6ac
9 changed files with 236 additions and 61 deletions

View File

@ -185,11 +185,11 @@ public sealed class DX11ScreenCapture : IScreenCapture
MappedSubresource mapSource = _context.Map(stagingTexture, 0, MapMode.Read, MapFlags.None);
IntPtr sourcePtr = mapSource.DataPointer;
lock (captureZone.Buffer)
lock (captureZone.PixelBuffer)
{
for (int y = 0; y < captureZone.Height; y++)
{
Marshal.Copy(sourcePtr, captureZone.Buffer, y * captureZone.Stride, captureZone.Stride);
Marshal.Copy(sourcePtr, captureZone.PixelBuffer.Raw, y * captureZone.Stride, captureZone.Stride);
sourcePtr += mapSource.RowPitch;
}
}
@ -209,8 +209,7 @@ public sealed class DX11ScreenCapture : IScreenCapture
int unscaledHeight = height;
(width, height) = CalculateScaledSize(unscaledWidth, unscaledHeight, downscaleLevel);
byte[] buffer = new byte[width * height * BPP];
PixelBuffer buffer = new PixelBuffer<ColorBGRA8>(width, height);
CaptureZone captureZone = new(_indexCounter++, x, y, width, height, BPP, downscaleLevel, unscaledWidth, unscaledHeight, buffer);
lock (_captureZones)
InitializeCaptureZone(captureZone);
@ -268,7 +267,7 @@ public sealed class DX11ScreenCapture : IScreenCapture
captureZone.Width = newWidth;
captureZone.Height = newHeight;
captureZone.DownscaleLevel = newDownscaleLevel;
captureZone.Buffer = new byte[newWidth * newHeight * BPP];
captureZone.PixelBuffer = new PixelBuffer<ColorBGRA8>(newWidth, newHeight);
InitializeCaptureZone(captureZone);
}
@ -305,18 +304,18 @@ public sealed class DX11ScreenCapture : IScreenCapture
private void InitializeCaptureZone(in CaptureZone captureZone)
{
Texture2DDescription stagingTextureDesc = new()
{
CpuAccessFlags = CpuAccessFlags.Read,
BindFlags = BindFlags.None,
Format = Format.B8G8R8A8_UNorm,
Width = captureZone.Width,
Height = captureZone.Height,
OptionFlags = ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = ResourceUsage.Staging
};
{
CpuAccessFlags = CpuAccessFlags.Read,
BindFlags = BindFlags.None,
Format = Format.B8G8R8A8_UNorm,
Width = captureZone.Width,
Height = captureZone.Height,
OptionFlags = ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = ResourceUsage.Staging
};
ID3D11Texture2D stagingTexture = _device!.CreateTexture2D(stagingTextureDesc);
ID3D11Texture2D? scalingTexture = null;
@ -324,18 +323,18 @@ public sealed class DX11ScreenCapture : IScreenCapture
if (captureZone.DownscaleLevel > 0)
{
Texture2DDescription scalingTextureDesc = new()
{
CpuAccessFlags = CpuAccessFlags.None,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
Format = Format.B8G8R8A8_UNorm,
Width = captureZone.UnscaledWidth,
Height = captureZone.UnscaledHeight,
OptionFlags = ResourceOptionFlags.GenerateMips,
MipLevels = captureZone.DownscaleLevel + 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = ResourceUsage.Default
};
{
CpuAccessFlags = CpuAccessFlags.None,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
Format = Format.B8G8R8A8_UNorm,
Width = captureZone.UnscaledWidth,
Height = captureZone.UnscaledHeight,
OptionFlags = ResourceOptionFlags.GenerateMips,
MipLevels = captureZone.DownscaleLevel + 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = ResourceUsage.Default
};
scalingTexture = _device!.CreateTexture2D(scalingTextureDesc);
scalingTextureView = _device.CreateShaderResourceView(scalingTexture);
}
@ -362,18 +361,18 @@ public sealed class DX11ScreenCapture : IScreenCapture
using IDXGIOutput5 output = _output.QueryInterface<IDXGIOutput5>();
Texture2DDescription captureTextureDesc = new()
{
CpuAccessFlags = CpuAccessFlags.None,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
Format = Format.B8G8R8A8_UNorm,
Width = Display.Width,
Height = Display.Height,
OptionFlags = ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = ResourceUsage.Default
};
{
CpuAccessFlags = CpuAccessFlags.None,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
Format = Format.B8G8R8A8_UNorm,
Width = Display.Width,
Height = Display.Height,
OptionFlags = ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = ResourceUsage.Default
};
_captureTexture = _device.CreateTexture2D(captureTextureDesc);
lock (_captureZones)
@ -383,7 +382,7 @@ public sealed class DX11ScreenCapture : IScreenCapture
}
if (_useNewDuplicationAdapter)
_duplicatedOutput = output.DuplicateOutput1(_device, Format.B8G8R8A8_UNorm); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth
_duplicatedOutput = output.DuplicateOutput1(_device, new[] { Format.B8G8R8A8_UNorm }); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth
else
_duplicatedOutput = output.DuplicateOutput(_device);
}

View File

@ -0,0 +1,39 @@
using System.Runtime.CompilerServices;
namespace ScreenCapture.NET;
public static class MathHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Clamp(this float value, float min, float max)
{
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
if (value < min) return min;
if (value > max) return max;
return value;
// ReSharper restore ConvertIfStatementToReturnStatement
}
/// <summary>
/// Converts a normalized float value in the range [0..1] to a byte [0..255].
/// </summary>
/// <param name="percentage">The normalized float value to convert.</param>
/// <returns>The byte value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByteValueFromPercentage(this float percentage)
{
if (float.IsNaN(percentage)) return 0;
percentage = percentage.Clamp(0, 1.0f);
return (byte)(percentage >= 1.0f ? 255 : percentage * 256.0f);
}
/// <summary>
/// Converts a byte value [0..255] to a normalized float value in the range [0..1].
/// </summary>
/// <param name="value">The byte value to convert.</param>
/// <returns>The normalized float value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GetPercentageFromByteValue(this byte value)
=> value == 255 ? 1.0f : (value / 256.0f);
}

View File

@ -81,7 +81,7 @@ public sealed class BlackBarDetection
int stride = _captureZone.Stride;
for (int row = 0; row < _captureZone.Height; row++)
{
Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
Span<byte> data = new(_captureZone.PixelBuffer.Raw, row * stride, stride);
for (int i = 0; i < data.Length; i += 4)
if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
return row;
@ -96,7 +96,7 @@ public sealed class BlackBarDetection
int stride = _captureZone.Stride;
for (int row = _captureZone.Height - 1; row >= 0; row--)
{
Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
Span<byte> data = new(_captureZone.PixelBuffer.Raw, row * stride, stride);
for (int i = 0; i < data.Length; i += 4)
if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
return (_captureZone.Height - 1) - row;
@ -109,7 +109,7 @@ public sealed class BlackBarDetection
{
int threshold = Threshold;
int stride = _captureZone.Stride;
byte[] buffer = _captureZone.Buffer;
byte[] buffer = _captureZone.PixelBuffer.Raw;
for (int column = 0; column < _captureZone.Width; column++)
for (int row = 0; row < _captureZone.Height; row++)
{
@ -125,7 +125,7 @@ public sealed class BlackBarDetection
{
int threshold = Threshold;
int stride = _captureZone.Stride;
byte[] buffer = _captureZone.Buffer;
byte[] buffer = _captureZone.PixelBuffer.Raw;
for (int column = _captureZone.Width - 1; column >= 0; column--)
for (int row = 0; row < _captureZone.Height; row++)
{

View File

@ -19,37 +19,58 @@ public sealed class CaptureZone
/// <summary>
/// Gets the x-location of the region on the screen.
/// </summary>
public int X { get; internal set; }
/// <remarks>
/// Should only be set inside of ScreenCaptures!
/// </remarks>
public int X { get; set; }
/// <summary>
/// Gets the y-location of the region on the screen.
/// </summary>
public int Y { get; internal set; }
/// <remarks>
/// Should only be set inside of ScreenCaptures!
/// </remarks>
public int Y { get; set; }
/// <summary>
/// Gets the width of the captured region.
/// </summary>
public int Width { get; internal set; }
/// <remarks>
/// Should only be set inside of ScreenCaptures!
/// </remarks>
public int Width { get; set; }
/// <summary>
/// Gets the height of the captured region.
/// </summary>
public int Height { get; internal set; }
/// <remarks>
/// Should only be set inside of ScreenCaptures!
/// </remarks>
public int Height { get; set; }
/// <summary>
/// Gets the level of downscaling applied to the image of this region before copying to local memory. The calculation is (width and height)/2^downscaleLevel.
/// </summary>
public int DownscaleLevel { get; internal set; }
/// <remarks>
/// Should only be set inside of ScreenCaptures!
/// </remarks>
public int DownscaleLevel { get; set; }
/// <summary>
/// Gets the original width of the region (this equals <see cref="Width"/> if <see cref="DownscaleLevel"/> is 0).
/// </summary>
public int UnscaledWidth { get; internal set; }
/// <remarks>
/// Should only be set inside of ScreenCaptures!
/// </remarks>
public int UnscaledWidth { get; set; }
/// <summary>
/// Gets the original height of the region (this equals <see cref="Height"/> if <see cref="DownscaleLevel"/> is 0).
/// </summary>
public int UnscaledHeight { get; internal set; }
/// <remarks>
/// Should only be set inside of ScreenCaptures!
/// </remarks>
public int UnscaledHeight { get; set; }
/// <summary>
/// Gets the amount of bytes per pixel in the image (most likely 3 [RGB] or 4 [ARGB]).
@ -64,7 +85,10 @@ public sealed class CaptureZone
/// <summary>
/// Gets the buffer containing the image data. Format depends on the specific capture but is most likely BGRA32.
/// </summary>
public byte[] Buffer { get; internal set; }
/// <remarks>
/// Should only be set inside of ScreenCaptures!
/// </remarks>
public PixelBuffer PixelBuffer { get; set; }
/// <summary>
/// Gets the config for black-bar detection.
@ -107,7 +131,7 @@ public sealed class CaptureZone
/// <param name="unscaledWidth">The original width of the region.</param>
/// <param name="unscaledHeight">The original height of the region</param>
/// <param name="buffer">The buffer containing the image data.</param>
internal CaptureZone(int id, int x, int y, int width, int height, int bytesPerPixel, int downscaleLevel, int unscaledWidth, int unscaledHeight, byte[] buffer)
public CaptureZone(int id, int x, int y, int width, int height, int bytesPerPixel, int downscaleLevel, int unscaledWidth, int unscaledHeight, PixelBuffer buffer)
{
this.Id = id;
this.X = x;
@ -118,7 +142,7 @@ public sealed class CaptureZone
this.UnscaledWidth = unscaledWidth;
this.UnscaledHeight = unscaledHeight;
this.DownscaleLevel = downscaleLevel;
this.Buffer = buffer;
this.PixelBuffer = buffer;
BlackBars = new BlackBarDetection(this);
}

View File

@ -0,0 +1,41 @@
using System.Runtime.InteropServices;
namespace ScreenCapture.NET;
[StructLayout(LayoutKind.Sequential)]
public readonly struct ColorBGRA8 : IColor
{
#region Properties & Fields
private readonly byte _r;
private readonly byte _g;
private readonly byte _b;
private readonly byte _a;
public byte A => _a;
public byte R => _r;
public byte G => _g;
public byte B => _b;
public float sA => _a.GetPercentageFromByteValue();
public float sR => _r.GetPercentageFromByteValue();
public float sG => _g.GetPercentageFromByteValue();
public float sB => _b.GetPercentageFromByteValue();
#endregion
#region Constructors
public ColorBGRA8()
{ }
public ColorBGRA8(byte r, byte g, byte b, byte a)
{
this._r = r;
this._g = g;
this._b = b;
this._a = a;
}
#endregion
}

View File

@ -0,0 +1,14 @@
namespace ScreenCapture.NET;
public interface IColor
{
byte A { get; }
byte R { get; }
byte G { get; }
byte B { get; }
float sA { get; }
float sR { get; }
float sG { get; }
float sB { get; }
}

View File

@ -0,0 +1,56 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CommunityToolkit.HighPerformance;
namespace ScreenCapture.NET;
public abstract class PixelBuffer
{
#region Properties & Fields
public byte[] Raw { get; }
public abstract ReadOnlySpan2D<IColor> Pixels { get; }
#endregion
#region Constructors
protected PixelBuffer(byte[] buffer)
{
this.Raw = buffer;
}
#endregion
}
public sealed class PixelBuffer<TColor> : PixelBuffer
where TColor : unmanaged, IColor
{
#region Properties & Fields
private readonly int _width;
private readonly int _height;
public override unsafe ReadOnlySpan2D<IColor> Pixels
{
get
{
TColor @ref = MemoryMarshal.AsRef<TColor>(Raw);
return new ReadOnlySpan2D<IColor>(Unsafe.AsPointer(ref @ref), _height, _width, 0);
}
}
#endregion
#region Constructors
public PixelBuffer(int width, int height)
: base(new byte[width * height * Marshal.SizeOf<TColor>()])
{
this._width = width;
this._height = height;
}
#endregion
}

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net5.0</TargetFrameworks>
<TargetFrameworks>net7.0;net6.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@ -31,9 +31,9 @@
- Fixed ambiguous equals in Display
</PackageReleaseNotes>
<Version>1.2.0</Version>
<AssemblyVersion>1.2.0</AssemblyVersion>
<FileVersion>1.2.0</FileVersion>
<Version>2.0.0</Version>
<AssemblyVersion>2.0.0</AssemblyVersion>
<FileVersion>2.0.0</FileVersion>
<OutputPath>..\bin\</OutputPath>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@ -64,6 +64,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.1.0" />
<PackageReference Include="Vortice.Direct3D11" Version="1.9.143" />
</ItemGroup>

View File

@ -2,4 +2,5 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=directx/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=helper/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=model/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=model/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=model_005Ccolors/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>