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. program()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. program(cl_program program,bool retain=true)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. program(const program & 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. operator =(const program & other)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. program(program && 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. operator =(program && other)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. ~program()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. get() const152 cl_program& get() const 153 { 154 return const_cast<cl_program &>(m_program); 155 } 156 157 /// Returns the source code for the program. source() const158 std::string source() const 159 { 160 return get_info<std::string>(CL_PROGRAM_SOURCE); 161 } 162 163 /// Returns the binary for the program. binary() const164 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 get_devices() const182 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. get_context() const196 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> get_info(cl_program_info info) const205 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> get_build_info(cl_program_build_info info,const device & device) const226 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} build(const std::string & options=std::string ())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} compile(const std::string & options=std::string ())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} link(const std::vector<program> & programs,const context & context,const std::string & options=std::string ())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. build_log() const335 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. operator ==(const program & other) const350 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. operator !=(const program & other) const356 bool operator!=(const program &other) const 357 { 358 return m_program != other.m_program; 359 } 360 361 /// \internal_ operator cl_program() const362 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} create_with_source(const std::string & source,const context & context)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} create_with_source(const std::vector<std::string> & sources,const context & context)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} create_with_source_file(const std::string & file,const context & context)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} create_with_binary(const unsigned char * binary,size_t binary_size,const context & context)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} create_with_binary(const std::vector<unsigned char> & binary,const context & context)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} create_with_binary_file(const std::string & file,const context & context)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} create_with_builtin_kernels(const context & context,const std::vector<device> & devices,const std::string & kernel_names)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 */ build_with_source(const std::string & source,const context & context,const std::string & options=std::string ())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. save_program_binary(const std::string & hash,const program & prog)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. load_program_binary(const std::string & hash,const context & ctx)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 651