In this post we will write a simple Windows UWP application that use SharpCL, an easy to use OpenCL .NET open source library.

We will see how to apply some effects to an image, using the GPU to perform all the calculations. This post assumes at least a basic understanding of how OpenCL works, but I will try to recall the main elements of the OpenCL framework when needed. To learn more about OpenCL, visit Khronos group’s site.

I will not show all the application code, but only the parts related to the library. You can find the complete code in the SharpCL GitHub repository.

OpenCL expose the computing hardware as a series of Platforms (hosts), which in turn contain a series of Devices. An OpenCL application run on a host and submits commands from it to host’s Devices. To do this, you need to create what is called a Context. A context includes the following resources:

  • Devices
  • Kernels, i.e. the OpenCL functions that run on the devices
  • Memory Objects, i.e. objects that contains data used by the kernels (buffers and images)

Then, the host application creates a data structure called Command Queue to coordinate execution of the kernels on the devices. The host places commands into the command queue which are then scheduled onto the devices within the context. Commands include kernels execution, memory transfers and synchronization commands.

 

Initializing SharpCL

SharpCL contains various classes and methods to manage platforms and devices and to create contexts and command queues. In this example, we will use the simplest procedure, that automatically create a Context object that use devices of the selected type, then create a CommandQueue object from the context:

context = Context.AutomaticContext(DeviceType.GPU);
if (context == null) {
	// Error
	...
}
commandQueue = context.CreateCommandQueue();
if(commandQueue == null) {
	// Error
	...
}

Kernels are written with the OpenCL C programming language and built with a simple method that returns a dictionary that associates the kernel names with the compiled Kernel objects:

string kernelsCode = "..."; //Kernels source code
kernels = context.BuildAllKernels(kernelsCode);
if(context.Error) {
	...
}

In our example, we will use two simple kernels. The first read an input image, blur every pixel averaging a 7×7 pixels square around it and output the result on a destination image:

__kernel void blur(read_only image2d_t source, write_only image2d_t destination) {
	// Get pixel coordinate
	int2 coord = (int2)(get_global_id(0), get_global_id(1));

	// Create a sampler that use edge color for coordinates outside the image
	const sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAREST;

	// Blur using colors in a 7x7 square
	uint4 color = (uint4)(0, 0, 0, 255);
	for(int u=-3; u<=3; u++) {
		for(int v=-3; v<=3; v++) {
			color += read_imageui(source, sampler, coord + (int2)(u, v));
		}
	}
	color /= 49;

	// Write blurred pixel in destination image
	write_imageui(destination, coord, color);
}

and the second one simply invert the image color of every pixel:

__kernel void invert(read_only image2d_t source, write_only image2d_t destination) {
	// Get pixel coordinate
	int2 coord = (int2)(get_global_id(0), get_global_id(1));

	// Read color ad invert it (except for alpha value)
	uint4 color = read_imageui(source, coord);
	color.xyz = (uint3)(255,255,255) - color.xyz;

	// Write inverted pixel in destination image
	write_imageui(destination, coord, color);
}

 

Executing a kernel

To execute one of the previous kernel, we need to follow these steps:

1) Get the source image data from a UWP Image (named in this example SourceImage) and create an OpenCL memory object to store this data:

WriteableBitmap sourceBitmap = SourceImage.Source as WriteableBitmap;
byte[] sourceData = sourceBitmap.PixelBuffer.ToArray();
SharpCL.Image sourceImage = context.CreateImage2D(sourceData, (ulong)sourceBitmap.PixelWidth, (ulong)sourceBitmap.PixelHeight, MemoryFlags.ReadOnly | MemoryFlags.CopyHostPointer, ImageChannelOrder.BGRA, ImageChannelType.UnsignedInt8);

2) Create a memory object that will contain the destination image data:

SharpCL.Image destinationImage = context.CreateImage2D((ulong)sourceBitmap.PixelWidth, (ulong)sourceBitmap.PixelHeight, MemoryFlags.WriteOnly, ImageChannelOrder.BGRA, ImageChannelType.UnsignedInt8);

3) Set the source and destination memory objects as arguments of the Kernel:

kernels[kernelName].SetArgument(0, sourceImage);
kernels[kernelName].SetArgument(1, destinationImage);

where kernelName can be “blur” or “invert”, depending on which kernel you want to execute.

4) Add the kernel execution command to the command queue:

Event kernelEvent = commandQueue.EnqueueKernel(kernels[kernelName], new ulong[] { (ulong)sourceBitmap.PixelWidth, (ulong)sourceBitmap.PixelHeight });
if (commandQueue.Error) {
	...
}

5) Add a read image command to the command queue to retrieve destination image data:

byte[] destinationData = new byte[sourceBitmap.PixelWidth * sourceBitmap.PixelHeight * 4];
commandQueue.EnqueueReadImage(destinationImage, destinationData, default, default, true, new List<Event> { kernelEvent });
if (commandQueue.Error) {
	...
}

6) Use the destination image data to create a source for a UWP Image (DestinationImage in this example):

WriteableBitmap writeableBitmap = new WriteableBitmap(sourceBitmap.PixelWidth, sourceBitmap.PixelHeight);
using (Stream stream = writeableBitmap.PixelBuffer.AsStream())
{
	await stream.WriteAsync(destinationData, 0, sourceBitmap.PixelWidth * sourceBitmap.PixelHeight * 4);
}
DestinationImage.Source = writeableBitmap;

 

Final results

The following images show the results of the execution of the two kernels:

Result of the blur kernel
Result of the invert kernel

This is the test image I used:

Markus Benesch’s horse for Leonardo Horse Project



0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to browse this Website, you consent to the use of cookies. More information

This Website uses:

By continuing to browse this Website without changing the cookies settings of your browser or by clicking "Accept" below, you consent to the use of cookies.

Close