Skip to content

Instantly share code, notes, and snippets.

@Sergio0694
Created August 28, 2019 15:19
Show Gist options
  • Save Sergio0694/90d1f37721f279168425043b32d0fdd1 to your computer and use it in GitHub Desktop.
Save Sergio0694/90d1f37721f279168425043b32d0fdd1 to your computer and use it in GitHub Desktop.
A benchmark between ImageSharp and ComputeSharp with a bokeh effect
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