[Tutorial] Sharing textures in OpenGL/OpenCL

OpenCL is a GPGPU (General-purpose computing on graphics processing units) framework, which allows us to use the GPU for massive parallel programming. Compared to OpenGL, which is API for (3D) computer graphics, OpenCL defines everything more general to let non-graphics applications benefit from the GPU Power. To some point you can also do this kind of things in OpenGL compute shaders, but OpenCL is more flexible.

In practice it happens often, that we want to make some expensive computation on images and show them in real-time. So the naive way would be using OpenCL/OpenGL to apply the algorithm and then use OpenCV to show the image. What happens is that we upload the source image to GPU memory, let the kernel run the algorithm and then download it back into RAM. After that we show the image, by loading it up to the GPU as a texture and show it on the screen (in the most cases this happens implicitly).

The Problem

Transferring the image between GPU memory and RAM every frame is a huge overhead . We can avoid this by sharing  memory between OpenCL and OpenGL and do all calculation, while the image stays in the GPU memory.

Conways Game-of-Life, or something similar is a good example doing that.

4k resolution (3840×2160) @ ~44 FPS with Geforce GTX 970. On the CPU you can´t get 44 FPS with VGA (640*480)

Use existing projects

I forked a github project, to get some existing code which used OpenCL and OpenMP.

Here is the forked GitHub project(Code here!).

Ugly code, just for demo.

Context Sharing

After we created a window with glut, the next step is toget the OpenGL context. Under Windows there is a function to get the current context.

cl_context_properties props[] =
		{
			CL_GL_CONTEXT_KHR, (cl_context_properties)wglGetCurrentContext(),
			CL_WGL_HDC_KHR, (cl_context_properties)wglGetCurrentDC(),
			CL_CONTEXT_PLATFORM, (cl_context_properties)(platforms[0])(),
			0
		};
cl::Context context = cl::Context(CL_DEVICE_TYPE_GPU, props);

First init the window like this:
glutInitWindowSize(800, 600);
glutCreateWindow(“Test”);

Create Textures

We need this two times to generate the textures.

		glGenTextures(1, &texture);
		glBindTexture(GL_TEXTURE_2D, texture);
		glBindTexture(GL_TEXTURE_2D, texture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);	
		glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, field.data());

We only need GL_LUMINANCE as we don´t have colors. glTexImage2D does trigger the upload and gives us the TextureHandle.

Create OpenCL Handle

Now we need a OpenCL data handle

		cl_int result;
		openCLTexture = cl::Image2DGL(context, CL_MEM_READ_WRITE, GL_TEXTURE_2D, 0, texture, &result);
		if (result != CL_SUCCESS) {
			std::cout << "Texture sharing failed";
		};

Require the objects ownership

OpenCL need to acquire the texture from OpenGL

std::vector memObjs;
memObjs.push_back(openCLTexture);
cl::Event ev;
	cl_int result = CL_SUCCESS;
	queue->enqueueAcquireGLObjects(&memObjs, NULL, &ev);
	ev.wait();

So everything is prepared now we can set up parameters for the kernel

gol->setArg(0, openCLTexture);
gol->setArg(1, openCLTextureSwap);

Execute the kernel

Run the kernel and wait for the kernel execution to finish. OpenCL defines global work as the dimension of the image. Local work (cl::NullRange) is a concept, which allows synchronization (e.g. line by line), but we don´t need a sync.

cl::NDRange globalWork(data.width, data.height);
cl::NDRange groupSize(1, 1);
queue->enqueueNDRangeKernel(*gol, cl::NullRange, globalWork, groupSize);
queue->enqueueReleaseGLObjects(&memObjs, NULL, &ev);
ev.wait();

When working with textures, we need to make clear that we use a texture unit in the kernel. There are some advantages (hardware interpolation possible, bounds handling etc.), which we don´t use here. Note that you can only read or write to an texture.

kernel void Gol_All(
	__read_only image2d_t input,
	__write_only  image2d_t output,
	int width,
	int height
	)
{
		const sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_FILTER_NEAREST | CLK_ADDRESS_CLAMP;
(...)

Rendering

Now lets render the texture into a quad.

glBindTexture(GL_TEXTURE_2D, nextTexture);	glLoadIdentity();	
glColor3f(1, 1, 1);
glEnable(GL_TEXTURE_2D);

// Draw a textured quad
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex3f(0, 0, 0);
glTexCoord2f(0, 1); glVertex3f(0, height, 0);
glTexCoord2f(1, 1); glVertex3f(width, height, 0);
glTexCoord2f(1, 0); glVertex3f(width, 0, 0);
glEnd();

glBindTexture(GL_TEXTURE_2D, 0);

Related Posts

2 comments

Hello!! Really liked the article (about sharing textures in openGL and openCL) and appreciate the effort.

I had a question: will the openCL be running on a different process than the openGL i.e. will this GPU memory be shared among two different processes (one process that uses openCL and other using openGL)???

Thanks and Regards,
Tanvi

Hi Tanvi, sorry for that late reply, I was not checking for comments. I have not tried running it in different processes, but in theory it should work. Cheers

Leave a reply