|
21 | 21 | // If OpenCL is disabled then don't provide this file
|
22 | 22 | #if !defined(VISUALMESH_DISABLE_OPENCL)
|
23 | 23 |
|
| 24 | +#include <fstream> |
24 | 25 | #include <iomanip>
|
25 | 26 | #include <numeric>
|
26 | 27 | #include <sstream>
|
@@ -63,56 +64,164 @@ namespace engine {
|
63 | 64 | // OpenCL ::clSetKernelArg functions take the sizeof a pointer as their argument, this is correct
|
64 | 65 | static constexpr size_t MEM_SIZE = sizeof(cl_mem);
|
65 | 66 |
|
66 |
| - public: |
67 | 67 | /**
|
68 |
| - * @brief Construct a new OpenCL Engine object |
| 68 | + * @brief Load an OpenCL binary from a file and build it |
| 69 | + * |
| 70 | + * @param binary_path path to save the binary file to |
| 71 | + * @param device OpenCL device id |
69 | 72 | *
|
70 |
| - * @param structure the network structure to use classification |
71 | 73 | */
|
72 |
| - Engine(const NetworkStructure<Scalar>& structure = {}) { |
| 74 | + void load_binary(const std::string& binary_path, cl_device_id& device) { |
| 75 | + // If the file doesn't exist, this isn't an error so don't throw just return that it didn't work |
| 76 | + std::ifstream read_binary(binary_path, std::ios::in); |
| 77 | + if (!read_binary) { throw std::runtime_error("Failed to read from precompiled OpenCL binary."); } |
| 78 | + |
| 79 | + // Error flag to check if any OpenCL functions fail |
| 80 | + cl_int error = CL_SUCCESS; |
| 81 | + |
| 82 | + // Get the length |
| 83 | + read_binary.seekg(0, read_binary.end); |
| 84 | + size_t binary_size = read_binary.tellg(); |
| 85 | + read_binary.seekg(0, read_binary.beg); |
| 86 | + |
| 87 | + // Read the binary file |
| 88 | + std::vector<char> binary_load(binary_size, 0); |
| 89 | + read_binary.read(binary_load.data(), binary_size); |
| 90 | + read_binary.close(); |
| 91 | + if (!read_binary) { throw std::runtime_error("Failed to read from precompiled OpenCL binary."); } |
| 92 | + |
| 93 | + // Create the program and build using the loaded binary |
| 94 | + cl_int binary_status = CL_SUCCESS; |
| 95 | + const unsigned char* binary_ptr = reinterpret_cast<unsigned char*>(binary_load.data()); |
| 96 | + |
| 97 | + program = cl::program( |
| 98 | + ::clCreateProgramWithBinary(context, 1, &device, &binary_size, &binary_ptr, &binary_status, &error), |
| 99 | + ::clReleaseProgram); |
| 100 | + throw_cl_error(error, "Failed to create program from binary"); |
73 | 101 |
|
74 |
| - // Create the OpenCL context and command queue |
75 |
| - cl_int error = CL_SUCCESS; |
76 |
| - cl_device_id device = nullptr; |
77 |
| - std::tie(context, device) = operation::make_context(); |
78 |
| - queue = operation::make_queue(context, device); |
| 102 | + error = ::clBuildProgram(program, |
| 103 | + 1, |
| 104 | + &device, |
| 105 | + "-cl-single-precision-constant -cl-fast-relaxed-math -cl-mad-enable", |
| 106 | + nullptr, |
| 107 | + nullptr); |
79 | 108 |
|
80 |
| - // Get program sources (this does concatenated strings) |
81 |
| - std::stringstream sources; |
82 |
| - sources << operation::get_scalar_defines(Scalar(0.0)); |
83 |
| - sources << PROJECT_EQUIDISTANT_CL; |
84 |
| - sources << PROJECT_EQUISOLID_CL; |
85 |
| - sources << PROJECT_RECTILINEAR_CL; |
86 |
| - sources << LOAD_IMAGE_CL; |
87 |
| - sources << operation::make_network(structure); |
| 109 | + // If it didn't work, log and throw an error |
| 110 | + if (error != CL_SUCCESS) { |
| 111 | + // Get program build log |
| 112 | + size_t used = 0; |
| 113 | + ::clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, nullptr, &used); |
| 114 | + std::vector<char> log(used); |
| 115 | + ::clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log.size(), log.data(), &used); |
| 116 | + // Throw an error with the build log |
| 117 | + throw_cl_error(error, |
| 118 | + "Error building OpenCL program\n" + std::string(log.begin(), log.begin() + used)); |
| 119 | + } |
| 120 | + } |
88 | 121 |
|
89 |
| - std::string source = sources.str(); |
90 |
| - const char* cstr = source.c_str(); |
91 |
| - size_t csize = source.size(); |
| 122 | + /** |
| 123 | + * @brief Build the OpenCL program |
| 124 | + * |
| 125 | + * @param device OpenCL device id |
| 126 | + * @param source OpenCL source information |
| 127 | + */ |
| 128 | + void build_from_source(cl_device_id& device, const std::string& source) { |
| 129 | + // Error flag to check if any OpenCL functions fail |
| 130 | + cl_int error = CL_SUCCESS; |
92 | 131 |
|
| 132 | + // Create the program and build |
| 133 | + const char* cstr = source.c_str(); |
| 134 | + size_t csize = source.size(); |
93 | 135 | program =
|
94 | 136 | cl::program(::clCreateProgramWithSource(context, 1, &cstr, &csize, &error), ::clReleaseProgram);
|
95 | 137 | throw_cl_error(error, "Error adding sources to OpenCL program");
|
96 | 138 |
|
97 |
| - // Compile the program |
98 | 139 | error = ::clBuildProgram(program,
|
99 | 140 | 0,
|
100 | 141 | nullptr,
|
101 | 142 | "-cl-single-precision-constant -cl-fast-relaxed-math -cl-mad-enable",
|
102 | 143 | nullptr,
|
103 | 144 | nullptr);
|
| 145 | + |
| 146 | + // If it didn't work, log and throw an error |
104 | 147 | if (error != CL_SUCCESS) {
|
105 | 148 | // Get program build log
|
106 | 149 | size_t used = 0;
|
107 | 150 | ::clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, nullptr, &used);
|
108 | 151 | std::vector<char> log(used);
|
109 | 152 | ::clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log.size(), log.data(), &used);
|
110 |
| - |
111 | 153 | // Throw an error with the build log
|
112 | 154 | throw_cl_error(error,
|
113 | 155 | "Error building OpenCL program\n" + std::string(log.begin(), log.begin() + used));
|
114 | 156 | }
|
| 157 | + } |
| 158 | + |
| 159 | + /** |
| 160 | + * @brief Save the current OpenCL program in a binary file |
| 161 | + * |
| 162 | + * @param binary_path path to save the binary file to |
| 163 | + */ |
| 164 | + void save_binary(std::string binary_path) { |
| 165 | + |
| 166 | + // Get the size of the binary to save |
| 167 | + size_t binary_size{}; |
| 168 | + clGetProgramInfo(program, CL_PROGRAM_BINARY_SIZES, sizeof(size_t), &binary_size, nullptr); |
| 169 | + |
| 170 | + // Get the data to save |
| 171 | + std::vector<char> binary_save(binary_size, 0); |
| 172 | + // Get an lvalue ptr to pass to clGetProgramInfo |
| 173 | + char* binary_ptr = binary_save.data(); |
| 174 | + clGetProgramInfo(program, CL_PROGRAM_BINARIES, binary_save.size(), &binary_ptr, nullptr); |
| 175 | + |
| 176 | + // Write to the file and close the file |
| 177 | + std::ofstream write_binary(binary_path, std::ofstream::binary); |
| 178 | + write_binary.write(binary_save.data(), binary_save.size()); |
| 179 | + write_binary.close(); |
| 180 | + } |
| 181 | + |
| 182 | + public: |
| 183 | + /** |
| 184 | + * @brief Construct a new OpenCL Engine object |
| 185 | + * |
| 186 | + * @param structure the network structure to use classification |
| 187 | + * @param cache_directory directory to save/load the compiled OpenCL binary |
| 188 | + */ |
| 189 | + Engine(const NetworkStructure<Scalar>& structure = {}, const std::string& cache_directory = "") { |
| 190 | + // Create the OpenCL context and command queue |
| 191 | + cl_int error = CL_SUCCESS; |
| 192 | + cl_device_id device = nullptr; |
| 193 | + std::tie(context, device) = operation::make_context(); |
| 194 | + queue = operation::make_queue(context, device); |
| 195 | + |
| 196 | + // Get program sources (this does concatenated strings) |
| 197 | + std::stringstream sources; |
| 198 | + sources << operation::get_scalar_defines(Scalar(0.0)); |
| 199 | + sources << PROJECT_EQUIDISTANT_CL; |
| 200 | + sources << PROJECT_EQUISOLID_CL; |
| 201 | + sources << PROJECT_RECTILINEAR_CL; |
| 202 | + sources << LOAD_IMAGE_CL; |
| 203 | + sources << operation::make_network(structure); |
| 204 | + |
| 205 | + std::string source = sources.str(); |
| 206 | + |
| 207 | + // The hash of the sources represents the name of the OpenCL compiled program binary file, so that a new |
| 208 | + // binary will be created for different sources |
| 209 | + const std::size_t source_hash = std::hash<std::string>{}(source); |
| 210 | + |
| 211 | + // If the compiled binary exists, read it |
| 212 | + std::string binary_path = cache_directory + "/" + std::to_string(source_hash) + ".bin"; |
| 213 | + |
| 214 | + // Try to read the binary |
| 215 | + try { |
| 216 | + load_binary(binary_path, device); |
| 217 | + } |
| 218 | + // The compiled binary doesn't exist, create it |
| 219 | + catch (std::exception& /* e */) { |
| 220 | + build_from_source(device, source); |
| 221 | + save_binary(binary_path); |
| 222 | + } |
115 | 223 |
|
| 224 | + // Get the kernels |
116 | 225 | project_rectilinear =
|
117 | 226 | cl::kernel(::clCreateKernel(program, "project_rectilinear", &error), ::clReleaseKernel);
|
118 | 227 | throw_cl_error(error, "Error getting project_rectilinear kernel");
|
@@ -513,9 +622,7 @@ namespace engine {
|
513 | 622 | // Cache for future runs
|
514 | 623 | device_points_cache[&mesh] = cl_points;
|
515 | 624 | }
|
516 |
| - else { |
517 |
| - cl_points = device_mesh->second; |
518 |
| - } |
| 625 | + else { cl_points = device_mesh->second; } |
519 | 626 |
|
520 | 627 | // First count the size of the buffer we will need to allocate
|
521 | 628 | int n_points = 0;
|
|
0 commit comments