Adding OpenCL to a JUCE project for easy GPU access - Working Method Included

Goal

As per this thread: GPU vs. CPU processing - what is the future for audio processing? Can we access the GPU now? If so, how?

I am trying to add the OpenCL Wrapper project here to a JUCE project: https://github.com/ProjectPhysX/OpenCL-Wrapper

OpenCL Wrapper Project

The OpenCL project is incredibly small and easy to run by itself, as explained at that GitHub link. Basic requirements listed there:

  • video card drivers installed (like NVIDIA) as they will typically install OpenCL automatically.
  • Intel CPU Runtime for OpenCL linked there.
  • Visual Studio 2022, Windows 10 SDK, and MSVC v142 in Visual Studio Installer

Then if you clone that project, just open the sln file and click Debug. It opens a basic window showing some simple arithmetic has been completed as per main.cpp, which has only:

#include "opencl.hpp"

int main() {
	Device device(select_device_with_most_flops()); // compile OpenCL C code for the fastest available device

	const uint N = 1024u; // size of vectors
	Memory<float> A(device, N); // allocate memory on both host and device
	Memory<float> B(device, N);
	Memory<float> C(device, N);

	Kernel add_kernel(device, N, "add_kernel", A, B, C); // kernel that runs on the device

	for(uint n=0u; n<N; n++) {
		A[n] = 3.0f; // initialize memory
		B[n] = 2.0f;
		C[n] = 1.0f;
	}

	print_info("Value before kernel execution: C[0] = "+to_string(C[0]));

	A.write_to_device(); // copy data from host memory to device memory
	B.write_to_device();
	add_kernel.run(); // run add_kernel on the device
	C.read_from_device(); // copy data from device memory to host memory

	print_info("Value after kernel execution: C[0] = "+to_string(C[0]));

	wait();
	return 0;
}

This is the simplified nature of the OpenCL wrapper. It allows very easy GPU utilization in principle.

Contents of OpenCL Wrapper Project

There are only the following files in the project:

kernel.cpp
kernel.hpp
main.cpp
opencl.hpp
utilities.hpp
cl.h
cl.hpp
cl_ext.h
cl_gl.h
cl_gl_ext.h
cl_platform.h
opencl.h
OpenCL.lib
libOpenCL.so

We don’t need main.cpp certainly when pulling this over to JUCE projects.

I note examining the project, I can’t figure out why he has them in the file folder structure he does. Or how this type of thing (related to that) works:

I mean, there is only one cl.h in the file structure, so I’m not sure what this accomplishes (?) for example here re: Apple or not… This is just a minor point of interest though.

The biggest problem stopping me: The .so and the .lib files - I’m not sure what to do with these at all.

Steps So Far

My approach is to (i) try to add the OpenCL Wrapper files to a JUCE project, then (ii) copy and paste the simple commands from the wrapper project’s test main.cpp above inside the JUCE project (with DBG substituted to output result instead of print_info).

I have tried the following:

  1. Copy and paste all OpenCL Wrapper project files listed above into an OpenGL folder inside my project (minus main.cpp).
  2. Drag these into Projucer file manager so they are seen as part of project.
  3. Open in Visual Studio.
  4. Fix now broken addresses of OpenCL Wrapper file paths regarding #include like above.
  5. Annoying - Manually rename all the OpenCL Wrapper to_string functions in utilities.hpp to cl_to_string - These are ambiguous with std::to_string and will break things otherwise.

(Re: step 5, it was minor mistake in my opinion that the OpenCL Wrapper designer named a critical function in his utilities.hpp as to_string which overlaps std::to_string. This must be renamed carefully or you will get ambiguous or other errors.)

Result

This lets me add OpenCL Wrapper functions in my JUCE project without errors while coding, but I can’t build without link errors. Presumably the step I am missing has to do with the .lib / .so files. I don’t know what I’m supposed to do with them.

I get the following errors on trying to build:

So I presume I must do something with the .lib/.so files to “link” them? As I presume that is the core function library that the wrapper code is supposed to be interacting with?

Question

Any thoughts on what the missing step might be then? Or any better way to handle bringing this over into a JUCE project?

Thanks for any help or ideas.

1 Like

I read a bit more about .lib files and as far as I can tell what JUCE wants us to do is create a module for it:

I tried following that. I copied all the OpenCL files from the wrapper project into a folder in my user_modules folder called opencl. I then added to opencl.h in this root folder a required declaration:

/*
BEGIN_JUCE_MODULE_DECLARATION

 ID:               opencl
 vendor:           opencl
 version:          1.0.0
 name:             OpenCL Wrapper
 description:      OpenCL Access
 website:          https://github.com/ProjectPhysX/OpenCL-Wrapper
 license:          ???

 dependencies:     
 OSXFrameworks:    
 iOSFrameworks:    
 linuxLibs:        

END_JUCE_MODULE_DECLARATION
*/

I moved all the other .h .hpp .cpp files into a subdirectory of the module.

I put the .lib file in \user_modules\opencl\libs\VisualStudio2022\x64\MD\OpenCL.lib

I believe this is where it’s supposed to go based on the above GitHub link. (?)

This allowed me to add it to the project in the Projucer by re-scanning user modules and selecting it to add.

However, it is still giving me the same linker errors after that (no difference from before).

Any ideas for solving this link issue? Did I miss something with the module? Is it maybe I have to add a list of something to the dependencies: of the module?

One step closer:

Had to put the OpenCL.lib file in the same folder as the sln generated by Projucer/Visual Studio and add in Projucer OpenCL.lib under External Library to Link

Supposedly can use relative paths in extra linker flags so you don’t have to add it to the build folder. But I couldn’t make anything work in Extra Linker Flags.

See:

Now I just have one error remaining which I don’t know how to fix or why its happening.

error LNK2019: unresolved external symbol "class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __cdecl get_opencl_c_code(void)" (?get_opencl_c_code@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ) referenced in function "public: __cdecl AudioPlugInAudioProcessor::AudioPlugInAudioProcessor(void)" (??0AudioPlugInAudioProcessor@@QEAA@XZ)

This is happening where I run the line:

	Device device(select_device_with_most_flops()); // compile OpenCL C code for the fastest available device

Which is my OpenGL wrapper test code line. Not sure what to do next.

Got it working. :slight_smile:

Full Workflow:

I post the full workflow I used to convert the OpenCL Wrapper project into JUCE as I presume the OpenCL Wrapper may be updated over time but likely not directly disappear. Similarly things may change in JUCE. So simply sharing the files without the workflow is likely less safe for future reference.

As long as this forum, the OpenCL Wrapper, and JUCE continue to exist this should work with perhaps minor alterations over time.

EDITING OPEN CL WRAPPER PROJECT FOR JUCE

  1. Clone OpenCL Wrapper here: https://github.com/ProjectPhysX/OpenCL-Wrapper
  2. Open project in Visual Studio 2022 and ensure runs to start with.
  3. Move all files inside OpenCL-Wrapper/src/OpenCL/include/CL/ to OpenCL-Wrapper/src/ so everything shares a same directory except the lib file.
  4. Right click project, select properties > C/C++ > remove the src/OpenCL/include/CL/ directory as an external include folder (as we have now moved the files out of here) - forget where exactly this setting is but it is there
  5. Select “Show All Files” to see the new files in Visual Studio as per https://stackoverflow.com/a/69812456, click them and set include in project to “true” so you can see them.
  6. Keep trying to build and fix #include errors from moving files until solved.
  7. Ensure can still build project.
  8. Open utilities.hpp and right click any to_string function there, rename to cl_to_string, and select ALL possible functions that come up to rename this way (due to conflict otherwise with std::to_string once you bring over to JUCE).
  9. Ensure can still build project (should still all work perfectly)

ADDING TO JUCE

  1. Copy all these .h/.hpp/.cpp files from the modified OpenCL Wrapper project into a folder like OpenCL inside your Projucer project directory and drag this folder into your Projucer File Directory to add it to project.
  2. Copy OpenCL.lib into your Builds/VisualStudio2022 folder and add it in the Projucer Exporters Visual Studio 2022 “External Libraries to Link” as OpenCL.lib.
  3. Comment out with /* */ the contents of the function print_message of utilities.hpp as this will break in JUCE now once ported over.
  4. Open anywhere in your JUCE project code in Visual Studio and add
    #include "OpenCL/opencl.hpp"
  5. Run this test code taken from the Open CL Wrapper main.cpp in that page (with modification made to DBG output):
	//=====================
	//TEST OPENCL
	//===================
	DBG("START OPENCL TEST");
	Device device(select_device_with_most_flops()); // compile OpenCL C code for the fastest available device

	const uint N = 1024u; // size of vectors
	Memory<float> A(device, N); // allocate memory on both host and device
	Memory<float> B(device, N);
	Memory<float> C(device, N);

	Kernel add_kernel(device, N, "add_kernel", A, B, C); // kernel that runs on the device

	for (uint n = 0u; n < N; n++) {
		A[n] = 3.0f; // initialize memory
		B[n] = 2.0f;
		C[n] = 1.0f;
	}

	DBG("Value before kernel execution: C[0] = " + cl_to_string(C[0]));

	A.write_to_device(); // copy data from host memory to device memory
	B.write_to_device();
	add_kernel.run(); // run add_kernel on the device
	C.read_from_device(); // copy data from device memory to host memory

	DBG("Value after kernel execution: C[0] = " + cl_to_string(C[0]));
	
	//===================
  1. You will see the debugged math performed on the GPU successfully.

:slight_smile:

2 Likes