]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | //---------------------------------------------------------------------------// |
2 | // Copyright (c) 2013 Kyle Lutz <kyle.r.lutz@gmail.com> | |
3 | // | |
4 | // Distributed under the Boost Software License, Version 1.0 | |
5 | // See accompanying file LICENSE_1_0.txt or copy at | |
6 | // http://www.boost.org/LICENSE_1_0.txt | |
7 | // | |
8 | // See http://boostorg.github.com/compute for more information. | |
9 | //---------------------------------------------------------------------------// | |
10 | ||
11 | #ifndef BOOST_COMPUTE_PROGRAM_HPP | |
12 | #define BOOST_COMPUTE_PROGRAM_HPP | |
13 | ||
14 | #include <string> | |
15 | #include <vector> | |
16 | #include <fstream> | |
17 | #include <streambuf> | |
18 | ||
19 | #ifdef BOOST_COMPUTE_DEBUG_KERNEL_COMPILATION | |
20 | #include <iostream> | |
21 | #endif | |
22 | ||
23 | #include <boost/compute/config.hpp> | |
24 | #include <boost/compute/context.hpp> | |
25 | #include <boost/compute/exception.hpp> | |
26 | #include <boost/compute/detail/assert_cl_success.hpp> | |
27 | ||
28 | #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE | |
29 | #include <sstream> | |
30 | #include <boost/optional.hpp> | |
31 | #include <boost/compute/platform.hpp> | |
32 | #include <boost/compute/detail/getenv.hpp> | |
33 | #include <boost/compute/detail/path.hpp> | |
34 | #include <boost/compute/detail/sha1.hpp> | |
35 | #endif | |
36 | ||
37 | namespace boost { | |
38 | namespace compute { | |
39 | ||
40 | class kernel; | |
41 | ||
42 | /// \class program | |
43 | /// \brief A compute program. | |
44 | /// | |
45 | /// The program class represents an OpenCL program. | |
46 | /// | |
47 | /// Program objects are created with one of the static \c create_with_* | |
48 | /// functions. For example, to create a program from a source string: | |
49 | /// | |
50 | /// \snippet test/test_program.cpp create_with_source | |
51 | /// | |
52 | /// And to create a program from a source file: | |
53 | /// \code | |
54 | /// boost::compute::program bar_program = | |
55 | /// boost::compute::program::create_with_source_file("/path/to/bar.cl", context); | |
56 | /// \endcode | |
57 | /// | |
58 | /// Once a program object has been succesfully created, it can be compiled | |
59 | /// using the \c build() method: | |
60 | /// \code | |
61 | /// // build the program | |
62 | /// foo_program.build(); | |
63 | /// \endcode | |
64 | /// | |
65 | /// Once the program is built, \ref kernel objects can be created using the | |
66 | /// \c create_kernel() method by passing their name: | |
67 | /// \code | |
68 | /// // create a kernel from the compiled program | |
69 | /// boost::compute::kernel foo_kernel = foo_program.create_kernel("foo"); | |
70 | /// \endcode | |
71 | /// | |
72 | /// \see kernel | |
73 | class program | |
74 | { | |
75 | public: | |
76 | /// Creates a null program object. | |
77 | program() | |
78 | : m_program(0) | |
79 | { | |
80 | } | |
81 | ||
82 | /// Creates a program object for \p program. If \p retain is \c true, | |
83 | /// the reference count for \p program will be incremented. | |
84 | explicit program(cl_program program, bool retain = true) | |
85 | : m_program(program) | |
86 | { | |
87 | if(m_program && retain){ | |
88 | clRetainProgram(m_program); | |
89 | } | |
90 | } | |
91 | ||
92 | /// Creates a new program object as a copy of \p other. | |
93 | program(const program &other) | |
94 | : m_program(other.m_program) | |
95 | { | |
96 | if(m_program){ | |
97 | clRetainProgram(m_program); | |
98 | } | |
99 | } | |
100 | ||
101 | /// Copies the program object from \p other to \c *this. | |
102 | program& operator=(const program &other) | |
103 | { | |
104 | if(this != &other){ | |
105 | if(m_program){ | |
106 | clReleaseProgram(m_program); | |
107 | } | |
108 | ||
109 | m_program = other.m_program; | |
110 | ||
111 | if(m_program){ | |
112 | clRetainProgram(m_program); | |
113 | } | |
114 | } | |
115 | ||
116 | return *this; | |
117 | } | |
118 | ||
119 | #ifndef BOOST_COMPUTE_NO_RVALUE_REFERENCES | |
120 | /// Move-constructs a new program object from \p other. | |
121 | program(program&& other) BOOST_NOEXCEPT | |
122 | : m_program(other.m_program) | |
123 | { | |
124 | other.m_program = 0; | |
125 | } | |
126 | ||
127 | /// Move-assigns the program from \p other to \c *this. | |
128 | program& operator=(program&& other) BOOST_NOEXCEPT | |
129 | { | |
130 | if(m_program){ | |
131 | clReleaseProgram(m_program); | |
132 | } | |
133 | ||
134 | m_program = other.m_program; | |
135 | other.m_program = 0; | |
136 | ||
137 | return *this; | |
138 | } | |
139 | #endif // BOOST_COMPUTE_NO_RVALUE_REFERENCES | |
140 | ||
141 | /// Destroys the program object. | |
142 | ~program() | |
143 | { | |
144 | if(m_program){ | |
145 | BOOST_COMPUTE_ASSERT_CL_SUCCESS( | |
146 | clReleaseProgram(m_program) | |
147 | ); | |
148 | } | |
149 | } | |
150 | ||
151 | /// Returns the underlying OpenCL program. | |
152 | cl_program& get() const | |
153 | { | |
154 | return const_cast<cl_program &>(m_program); | |
155 | } | |
156 | ||
157 | /// Returns the source code for the program. | |
158 | std::string source() const | |
159 | { | |
160 | return get_info<std::string>(CL_PROGRAM_SOURCE); | |
161 | } | |
162 | ||
163 | /// Returns the binary for the program. | |
164 | std::vector<unsigned char> binary() const | |
165 | { | |
166 | size_t binary_size = get_info<size_t>(CL_PROGRAM_BINARY_SIZES); | |
167 | std::vector<unsigned char> binary(binary_size); | |
168 | ||
169 | unsigned char *binary_ptr = &binary[0]; | |
170 | cl_int error = clGetProgramInfo(m_program, | |
171 | CL_PROGRAM_BINARIES, | |
172 | sizeof(unsigned char **), | |
173 | &binary_ptr, | |
174 | 0); | |
175 | if(error != CL_SUCCESS){ | |
176 | BOOST_THROW_EXCEPTION(opencl_error(error)); | |
177 | } | |
178 | ||
179 | return binary; | |
180 | } | |
181 | ||
182 | std::vector<device> get_devices() const | |
183 | { | |
184 | std::vector<cl_device_id> device_ids = | |
185 | get_info<std::vector<cl_device_id> >(CL_PROGRAM_DEVICES); | |
186 | ||
187 | std::vector<device> devices; | |
188 | for(size_t i = 0; i < device_ids.size(); i++){ | |
189 | devices.push_back(device(device_ids[i])); | |
190 | } | |
191 | ||
192 | return devices; | |
193 | } | |
194 | ||
195 | /// Returns the context for the program. | |
196 | context get_context() const | |
197 | { | |
198 | return context(get_info<cl_context>(CL_PROGRAM_CONTEXT)); | |
199 | } | |
200 | ||
201 | /// Returns information about the program. | |
202 | /// | |
203 | /// \see_opencl_ref{clGetProgramInfo} | |
204 | template<class T> | |
205 | T get_info(cl_program_info info) const | |
206 | { | |
207 | return detail::get_object_info<T>(clGetProgramInfo, m_program, info); | |
208 | } | |
209 | ||
210 | /// \overload | |
211 | template<int Enum> | |
212 | typename detail::get_object_info_type<program, Enum>::type | |
213 | get_info() const; | |
214 | ||
215 | /// Returns build information about the program. | |
216 | /// | |
217 | /// For example, this function can be used to retreive the options used | |
218 | /// to build the program: | |
219 | /// \code | |
220 | /// std::string build_options = | |
221 | /// program.get_build_info<std::string>(CL_PROGRAM_BUILD_OPTIONS); | |
222 | /// \endcode | |
223 | /// | |
224 | /// \see_opencl_ref{clGetProgramInfo} | |
225 | template<class T> | |
226 | T get_build_info(cl_program_build_info info, const device &device) const | |
227 | { | |
228 | return detail::get_object_info<T>(clGetProgramBuildInfo, m_program, info, device.id()); | |
229 | } | |
230 | ||
231 | /// Builds the program with \p options. | |
232 | /// | |
233 | /// If the program fails to compile, this function will throw an | |
234 | /// opencl_error exception. | |
235 | /// \code | |
236 | /// try { | |
237 | /// // attempt to compile to program | |
238 | /// program.build(); | |
239 | /// } | |
240 | /// catch(boost::compute::opencl_error &e){ | |
241 | /// // program failed to compile, print out the build log | |
242 | /// std::cout << program.build_log() << std::endl; | |
243 | /// } | |
244 | /// \endcode | |
245 | /// | |
246 | /// \see_opencl_ref{clBuildProgram} | |
247 | void build(const std::string &options = std::string()) | |
248 | { | |
249 | const char *options_string = 0; | |
250 | ||
251 | if(!options.empty()){ | |
252 | options_string = options.c_str(); | |
253 | } | |
254 | ||
255 | cl_int ret = clBuildProgram(m_program, 0, 0, options_string, 0, 0); | |
256 | ||
257 | #ifdef BOOST_COMPUTE_DEBUG_KERNEL_COMPILATION | |
258 | if(ret != CL_SUCCESS){ | |
259 | // print the error, source code and build log | |
260 | std::cerr << "Boost.Compute: " | |
261 | << "kernel compilation failed (" << ret << ")\n" | |
262 | << "--- source ---\n" | |
263 | << source() | |
264 | << "\n--- build log ---\n" | |
265 | << build_log() | |
266 | << std::endl; | |
267 | } | |
268 | #endif | |
269 | ||
270 | if(ret != CL_SUCCESS){ | |
271 | BOOST_THROW_EXCEPTION(opencl_error(ret)); | |
272 | } | |
273 | } | |
274 | ||
275 | #if defined(CL_VERSION_1_2) || defined(BOOST_COMPUTE_DOXYGEN_INVOKED) | |
276 | /// Compiles the program with \p options. | |
277 | /// | |
278 | /// \opencl_version_warning{1,2} | |
279 | /// | |
280 | /// \see_opencl_ref{clCompileProgram} | |
281 | void compile(const std::string &options = std::string()) | |
282 | { | |
283 | const char *options_string = 0; | |
284 | ||
285 | if(!options.empty()){ | |
286 | options_string = options.c_str(); | |
287 | } | |
288 | ||
289 | cl_int ret = clCompileProgram( | |
290 | m_program, 0, 0, options_string, 0, 0, 0, 0, 0 | |
291 | ); | |
292 | ||
293 | if(ret != CL_SUCCESS){ | |
294 | BOOST_THROW_EXCEPTION(opencl_error(ret)); | |
295 | } | |
296 | } | |
297 | ||
298 | /// Links the programs in \p programs with \p options in \p context. | |
299 | /// | |
300 | /// \opencl_version_warning{1,2} | |
301 | /// | |
302 | /// \see_opencl_ref{clLinkProgram} | |
303 | static program link(const std::vector<program> &programs, | |
304 | const context &context, | |
305 | const std::string &options = std::string()) | |
306 | { | |
307 | const char *options_string = 0; | |
308 | ||
309 | if(!options.empty()){ | |
310 | options_string = options.c_str(); | |
311 | } | |
312 | ||
313 | cl_int ret; | |
314 | cl_program program_ = clLinkProgram( | |
315 | context.get(), | |
316 | 0, | |
317 | 0, | |
318 | options_string, | |
319 | static_cast<uint_>(programs.size()), | |
320 | reinterpret_cast<const cl_program*>(&programs[0]), | |
321 | 0, | |
322 | 0, | |
323 | &ret | |
324 | ); | |
325 | ||
326 | if(!program_){ | |
327 | BOOST_THROW_EXCEPTION(opencl_error(ret)); | |
328 | } | |
329 | ||
330 | return program(program_, false); | |
331 | } | |
332 | #endif // CL_VERSION_1_2 | |
333 | ||
334 | /// Returns the build log. | |
335 | std::string build_log() const | |
336 | { | |
337 | return get_build_info<std::string>(CL_PROGRAM_BUILD_LOG, get_devices().front()); | |
338 | } | |
339 | ||
340 | /// Creates and returns a new kernel object for \p name. | |
341 | /// | |
342 | /// For example, to create the \c "foo" kernel (after the program has been | |
343 | /// created and built): | |
344 | /// \code | |
345 | /// boost::compute::kernel foo_kernel = foo_program.create_kernel("foo"); | |
346 | /// \endcode | |
347 | kernel create_kernel(const std::string &name) const; | |
348 | ||
349 | /// Returns \c true if the program is the same at \p other. | |
350 | bool operator==(const program &other) const | |
351 | { | |
352 | return m_program == other.m_program; | |
353 | } | |
354 | ||
355 | /// Returns \c true if the program is different from \p other. | |
356 | bool operator!=(const program &other) const | |
357 | { | |
358 | return m_program != other.m_program; | |
359 | } | |
360 | ||
361 | /// \internal_ | |
362 | operator cl_program() const | |
363 | { | |
364 | return m_program; | |
365 | } | |
366 | ||
367 | /// Creates a new program with \p source in \p context. | |
368 | /// | |
369 | /// \see_opencl_ref{clCreateProgramWithSource} | |
370 | static program create_with_source(const std::string &source, | |
371 | const context &context) | |
372 | { | |
373 | const char *source_string = source.c_str(); | |
374 | ||
375 | cl_int error = 0; | |
376 | cl_program program_ = clCreateProgramWithSource(context, | |
377 | uint_(1), | |
378 | &source_string, | |
379 | 0, | |
380 | &error); | |
381 | if(!program_){ | |
382 | BOOST_THROW_EXCEPTION(opencl_error(error)); | |
383 | } | |
384 | ||
385 | return program(program_, false); | |
386 | } | |
387 | ||
388 | /// Creates a new program with \p sources in \p context. | |
389 | /// | |
390 | /// \see_opencl_ref{clCreateProgramWithSource} | |
391 | static program create_with_source(const std::vector<std::string> &sources, | |
392 | const context &context) | |
393 | { | |
394 | std::vector<const char*> source_strings(sources.size()); | |
395 | for(size_t i = 0; i < sources.size(); i++){ | |
396 | source_strings[i] = sources[i].c_str(); | |
397 | } | |
398 | ||
399 | cl_int error = 0; | |
400 | cl_program program_ = clCreateProgramWithSource(context, | |
401 | uint_(sources.size()), | |
402 | &source_strings[0], | |
403 | 0, | |
404 | &error); | |
405 | if(!program_){ | |
406 | BOOST_THROW_EXCEPTION(opencl_error(error)); | |
407 | } | |
408 | ||
409 | return program(program_, false); | |
410 | } | |
411 | ||
412 | /// Creates a new program with \p file in \p context. | |
413 | /// | |
414 | /// \see_opencl_ref{clCreateProgramWithSource} | |
415 | static program create_with_source_file(const std::string &file, | |
416 | const context &context) | |
417 | { | |
418 | // open file stream | |
419 | std::ifstream stream(file.c_str()); | |
420 | ||
421 | if(stream.fail()){ | |
422 | BOOST_THROW_EXCEPTION(std::ios_base::failure("failed to create stream.")); | |
423 | } | |
424 | ||
425 | // read source | |
426 | std::string source( | |
427 | (std::istreambuf_iterator<char>(stream)), | |
428 | std::istreambuf_iterator<char>() | |
429 | ); | |
430 | ||
431 | // create program | |
432 | return create_with_source(source, context); | |
433 | } | |
434 | ||
435 | /// Creates a new program with \p binary of \p binary_size in | |
436 | /// \p context. | |
437 | /// | |
438 | /// \see_opencl_ref{clCreateProgramWithBinary} | |
439 | static program create_with_binary(const unsigned char *binary, | |
440 | size_t binary_size, | |
441 | const context &context) | |
442 | { | |
443 | const cl_device_id device = context.get_device().id(); | |
444 | ||
445 | cl_int error = 0; | |
446 | cl_int binary_status = 0; | |
447 | cl_program program_ = clCreateProgramWithBinary(context, | |
448 | uint_(1), | |
449 | &device, | |
450 | &binary_size, | |
451 | &binary, | |
452 | &binary_status, | |
453 | &error); | |
454 | if(!program_){ | |
455 | BOOST_THROW_EXCEPTION(opencl_error(error)); | |
456 | } | |
457 | if(binary_status != CL_SUCCESS){ | |
458 | BOOST_THROW_EXCEPTION(opencl_error(binary_status)); | |
459 | } | |
460 | ||
461 | return program(program_, false); | |
462 | } | |
463 | ||
464 | /// Creates a new program with \p binary in \p context. | |
465 | /// | |
466 | /// \see_opencl_ref{clCreateProgramWithBinary} | |
467 | static program create_with_binary(const std::vector<unsigned char> &binary, | |
468 | const context &context) | |
469 | { | |
470 | return create_with_binary(&binary[0], binary.size(), context); | |
471 | } | |
472 | ||
473 | /// Creates a new program with \p file in \p context. | |
474 | /// | |
475 | /// \see_opencl_ref{clCreateProgramWithBinary} | |
476 | static program create_with_binary_file(const std::string &file, | |
477 | const context &context) | |
478 | { | |
479 | // open file stream | |
480 | std::ifstream stream(file.c_str(), std::ios::in | std::ios::binary); | |
481 | ||
482 | // read binary | |
483 | std::vector<unsigned char> binary( | |
484 | (std::istreambuf_iterator<char>(stream)), | |
485 | std::istreambuf_iterator<char>() | |
486 | ); | |
487 | ||
488 | // create program | |
489 | return create_with_binary(&binary[0], binary.size(), context); | |
490 | } | |
491 | ||
492 | #if defined(CL_VERSION_1_2) || defined(BOOST_COMPUTE_DOXYGEN_INVOKED) | |
493 | /// Creates a new program with the built-in kernels listed in | |
494 | /// \p kernel_names for \p devices in \p context. | |
495 | /// | |
496 | /// \opencl_version_warning{1,2} | |
497 | /// | |
498 | /// \see_opencl_ref{clCreateProgramWithBuiltInKernels} | |
499 | static program create_with_builtin_kernels(const context &context, | |
500 | const std::vector<device> &devices, | |
501 | const std::string &kernel_names) | |
502 | { | |
503 | cl_int error = 0; | |
504 | ||
505 | cl_program program_ = clCreateProgramWithBuiltInKernels( | |
506 | context.get(), | |
507 | static_cast<uint_>(devices.size()), | |
508 | reinterpret_cast<const cl_device_id *>(&devices[0]), | |
509 | kernel_names.c_str(), | |
510 | &error | |
511 | ); | |
512 | ||
513 | if(!program_){ | |
514 | BOOST_THROW_EXCEPTION(opencl_error(error)); | |
515 | } | |
516 | ||
517 | return program(program_, false); | |
518 | } | |
519 | #endif // CL_VERSION_1_2 | |
520 | ||
521 | /// Create a new program with \p source in \p context and builds it with \p options. | |
522 | /** | |
523 | * In case BOOST_COMPUTE_USE_OFFLINE_CACHE macro is defined, | |
524 | * the compiled binary is stored for reuse in the offline cache located in | |
525 | * $HOME/.boost_compute on UNIX-like systems and in %APPDATA%/boost_compute | |
526 | * on Windows. | |
527 | */ | |
528 | static program build_with_source( | |
529 | const std::string &source, | |
530 | const context &context, | |
531 | const std::string &options = std::string() | |
532 | ) | |
533 | { | |
534 | #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE | |
535 | // Get hash string for the kernel. | |
536 | device d = context.get_device(); | |
537 | platform p = d.platform(); | |
538 | ||
539 | detail::sha1 hash; | |
540 | hash.process( p.name() ) | |
541 | .process( p.version() ) | |
542 | .process( d.name() ) | |
543 | .process( options ) | |
544 | .process( source ) | |
545 | ; | |
546 | ||
547 | // Try to get cached program binaries: | |
548 | try { | |
549 | boost::optional<program> prog = load_program_binary(hash, context); | |
550 | ||
551 | if (prog) { | |
552 | prog->build(options); | |
553 | return *prog; | |
554 | } | |
555 | } catch (...) { | |
556 | // Something bad happened. Fallback to normal compilation. | |
557 | } | |
558 | ||
559 | // Cache is apparently not available. Just compile the sources. | |
560 | #endif | |
561 | const char *source_string = source.c_str(); | |
562 | ||
563 | cl_int error = 0; | |
564 | cl_program program_ = clCreateProgramWithSource(context, | |
565 | uint_(1), | |
566 | &source_string, | |
567 | 0, | |
568 | &error); | |
569 | if(!program_){ | |
570 | BOOST_THROW_EXCEPTION(opencl_error(error)); | |
571 | } | |
572 | ||
573 | program prog(program_, false); | |
574 | prog.build(options); | |
575 | ||
576 | #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE | |
577 | // Save program binaries for future reuse. | |
578 | save_program_binary(hash, prog); | |
579 | #endif | |
580 | ||
581 | return prog; | |
582 | } | |
583 | ||
584 | private: | |
585 | #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE | |
586 | // Saves program binaries for future reuse. | |
587 | static void save_program_binary(const std::string &hash, const program &prog) | |
588 | { | |
589 | std::string fname = detail::program_binary_path(hash, true) + "kernel"; | |
590 | std::ofstream bfile(fname.c_str(), std::ios::binary); | |
591 | if (!bfile) return; | |
592 | ||
593 | std::vector<unsigned char> binary = prog.binary(); | |
594 | ||
595 | size_t binary_size = binary.size(); | |
596 | bfile.write((char*)&binary_size, sizeof(size_t)); | |
597 | bfile.write((char*)binary.data(), binary_size); | |
598 | } | |
599 | ||
600 | // Tries to read program binaries from file cache. | |
601 | static boost::optional<program> load_program_binary( | |
602 | const std::string &hash, const context &ctx | |
603 | ) | |
604 | { | |
605 | std::string fname = detail::program_binary_path(hash) + "kernel"; | |
606 | std::ifstream bfile(fname.c_str(), std::ios::binary); | |
607 | if (!bfile) return boost::optional<program>(); | |
608 | ||
609 | size_t binary_size; | |
610 | std::vector<unsigned char> binary; | |
611 | ||
612 | bfile.read((char*)&binary_size, sizeof(size_t)); | |
613 | ||
614 | binary.resize(binary_size); | |
615 | bfile.read((char*)binary.data(), binary_size); | |
616 | ||
617 | return boost::optional<program>( | |
618 | program::create_with_binary( | |
619 | binary.data(), binary_size, ctx | |
620 | ) | |
621 | ); | |
622 | } | |
623 | #endif // BOOST_COMPUTE_USE_OFFLINE_CACHE | |
624 | ||
625 | private: | |
626 | cl_program m_program; | |
627 | }; | |
628 | ||
629 | /// \internal_ define get_info() specializations for program | |
630 | BOOST_COMPUTE_DETAIL_DEFINE_GET_INFO_SPECIALIZATIONS(program, | |
631 | ((cl_uint, CL_PROGRAM_REFERENCE_COUNT)) | |
632 | ((cl_context, CL_PROGRAM_CONTEXT)) | |
633 | ((cl_uint, CL_PROGRAM_NUM_DEVICES)) | |
634 | ((std::vector<cl_device_id>, CL_PROGRAM_DEVICES)) | |
635 | ((std::string, CL_PROGRAM_SOURCE)) | |
636 | ((std::vector<size_t>, CL_PROGRAM_BINARY_SIZES)) | |
637 | ((std::vector<unsigned char *>, CL_PROGRAM_BINARIES)) | |
638 | ) | |
639 | ||
640 | #ifdef CL_VERSION_1_2 | |
641 | BOOST_COMPUTE_DETAIL_DEFINE_GET_INFO_SPECIALIZATIONS(program, | |
642 | ((size_t, CL_PROGRAM_NUM_KERNELS)) | |
643 | ((std::string, CL_PROGRAM_KERNEL_NAMES)) | |
644 | ) | |
645 | #endif // CL_VERSION_1_2 | |
646 | ||
647 | } // end compute namespace | |
648 | } // end boost namespace | |
649 | ||
650 | #endif // BOOST_COMPUTE_PROGRAM_HPP |