Created
August 28, 2019 15:19
-
-
Save Sergio0694/90d1f37721f279168425043b32d0fdd1 to your computer and use it in GitHub Desktop.
A benchmark between ImageSharp and ComputeSharp with a bokeh effect
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Numerics; | |
using System.Reflection; | |
using System.Runtime.CompilerServices; | |
using System.Threading.Tasks; | |
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Running; | |
using ComputeSharp; | |
using SixLabors.ImageSharp; | |
using SixLabors.ImageSharp.Advanced; | |
using SixLabors.ImageSharp.PixelFormats; | |
using SixLabors.ImageSharp.Processing; | |
namespace ComputeSharpNuGetTest | |
{ | |
public class Program | |
{ | |
/// <summary> | |
/// The radius of the bokeh blur effect to apply | |
/// </summary> | |
private const int Radius = 32; | |
/// <summary> | |
/// The gamma exposure value to use when applying the effect | |
/// </summary> | |
private const float Gamma = 3; | |
/// <summary> | |
/// The inverse gamma exposure value to use when applying the effect | |
/// </summary> | |
private const float InverseGamma = 1 / Gamma; | |
public static void Main() | |
{ | |
BenchmarkRunner.Run<BokehTest>(); | |
} | |
public class BokehTest | |
{ | |
[Params("cyberpunk_large.png", "cyberpunk_medium.png")] | |
public string Filename; | |
private Image<Rgba32> Image1; | |
private Image<Rgba32> Image2; | |
[GlobalSetup] | |
public void Setup() | |
{ | |
string path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), Filename); | |
using Image<Rgba32> image = Image.Load<Rgba32>(path); | |
Image1 = image.Clone(); | |
Image2 = image.Clone(); | |
GpuBokeh(image); // Compile the HLSL shader | |
} | |
[GlobalCleanup] | |
public void Cleanup() | |
{ | |
Image1.Dispose(); | |
Image2.Dispose(); | |
} | |
[Benchmark] | |
public void Cpu() => Image1.Mutate(c => c.BokehBlur(Radius, 2, 3)); | |
[Benchmark] | |
public void Gpu() => GpuBokeh(Image2); | |
} | |
private static void GpuBokeh(Image<Rgba32> image) | |
{ | |
// Get the vector buffer | |
int height = image.Height, width = image.Width; | |
Vector4[] vectorArray = new Vector4[height * width]; | |
// Populate the buffer | |
Parallel.For(0, height, i => | |
{ | |
ref Rgba32 rPixel = ref image.GetPixelRowSpan(i).GetPinnableReference(); | |
ref Vector4 r4 = ref vectorArray[i * width]; | |
for (int j = 0; j < width; j++) | |
{ | |
Vector4 v4 = Unsafe.Add(ref rPixel, j).ToVector4(); | |
v4.X = MathF.Pow(v4.X, Gamma); | |
v4.Y = MathF.Pow(v4.Y, Gamma); | |
v4.Z = MathF.Pow(v4.Z, Gamma); | |
Unsafe.Add(ref r4, j) = v4; | |
} | |
}); | |
// Create the kernel | |
int diameter = Radius * 2 + 1; | |
float[] kernel = new float[diameter * diameter]; | |
int ones = 0; | |
for (int i = 0; i < diameter; i++) | |
{ | |
for (int j = 0; j < diameter; j++) | |
{ | |
if (MathF.Sqrt(MathF.Pow(j - Radius, 2) + MathF.Pow(i - Radius, 2)) - 0.1f <= Radius) | |
{ | |
kernel[i * diameter + j] = 1; | |
ones++; | |
} | |
} | |
} | |
// Normalize the kernel | |
for (int i = 0; i < diameter; i++) | |
for (int j = 0; j < diameter; j++) | |
kernel[i * diameter + j] /= ones; | |
using (ReadOnlyBuffer<Vector4> image_gpu = Gpu.Default.AllocateReadOnlyBuffer(vectorArray)) | |
using (ReadOnlyBuffer<float> kernel_gpu = Gpu.Default.AllocateReadOnlyBuffer(kernel)) | |
using (ReadWriteBuffer<Vector4> result_gpu = Gpu.Default.AllocateReadWriteBuffer<Vector4>(vectorArray.Length)) | |
{ | |
// Apply the effect | |
Gpu.Default.For(height, width, id => | |
{ | |
Vector4 total = Vector4.Zero; | |
for (int y = -Radius; y <= Radius; y++) | |
{ | |
for (int x = -Radius; x <= Radius; x++) | |
{ | |
int iy = id.X + y; | |
int jx = id.Y + x; | |
if (iy < 0) iy = -iy; | |
else if (iy > height) iy = 2 * height - iy; | |
if (jx < 0) jx = -jx; | |
else if (jx > width) jx = 2 * width - jx; | |
int ki = Radius - y; | |
int kj = Radius - x; | |
total += image_gpu[iy * width + jx] * kernel_gpu[ki * diameter + kj]; | |
} | |
} | |
result_gpu[id.X * width + id.Y] = total; | |
}); | |
// Copy data back | |
result_gpu.GetData(vectorArray); | |
} | |
// Copy the modified image back | |
Parallel.For(0, height, i => | |
{ | |
ref Rgba32 rPixel = ref image.GetPixelRowSpan(i).GetPinnableReference(); | |
ref Vector4 r4 = ref vectorArray[i * width]; | |
Vector4 low = Vector4.Zero; | |
Vector4 high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); | |
for (int j = 0; j < width; j++) | |
{ | |
Vector4 v4 = Unsafe.Add(ref r4, j); | |
Vector4 clamp = Vector4.Clamp(v4, low, high); | |
v4.X = MathF.Pow(clamp.X, InverseGamma); | |
v4.Y = MathF.Pow(clamp.Y, InverseGamma); | |
v4.Z = MathF.Pow(clamp.Z, InverseGamma); | |
Unsafe.Add(ref rPixel, j).FromVector4(v4); | |
} | |
}); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment