Cycles:
authorBrecht Van Lommel <brechtvanlommel@pandora.be>
Fri, 9 Sep 2011 12:04:39 +0000 (12:04 +0000)
committerBrecht Van Lommel <brechtvanlommel@pandora.be>
Fri, 9 Sep 2011 12:04:39 +0000 (12:04 +0000)
* OpenCL now only uses GPU/Accelerator devices, it's only confusing if CPU
  device is used, easy to enable in the code for debugging.
* OpenCL kernel binaries are now cached for faster startup after the first
  time compiling.
* CUDA kernels can now be compiled and cached at runtime if the CUDA toolkit
  is installed. This means that even if the build does not have CUDA enabled,
  it's still possible to use it as long as you install the toolkit.

intern/cycles/CMakeLists.txt
intern/cycles/blender/addon/engine.py
intern/cycles/blender/blender_python.cpp
intern/cycles/device/device_cuda.cpp
intern/cycles/device/device_opencl.cpp
intern/cycles/kernel/CMakeLists.txt
intern/cycles/kernel/kernel_compat_cuda.h
intern/cycles/render/session.cpp
intern/cycles/util/util_cuda.cpp
intern/cycles/util/util_path.cpp
intern/cycles/util/util_path.h

index 5d76f57fa73ca5287efd8235c95c166c267fb25c..05b9d4ad3bba335b7a31be189baad21988ef4cb9 100644 (file)
@@ -46,7 +46,7 @@ if(WITH_CYCLES_MULTI)
 endif()
 
 if(WITH_CYCLES_CUDA)
-  add_definitions(-DWITH_CUDA)
+  add_definitions(-DWITH_CUDA_BINARIES)
 endif()
 
 if(WITH_CYCLES_OSL)
@@ -58,6 +58,7 @@ if(WITH_CYCLES_PARTIO)
 endif()
 
 add_definitions(-DWITH_OPENCL)
+add_definitions(-DWITH_CUDA)
 
 include_directories(
        ${BOOST_INCLUDE_DIR}
index d6ea15a435ff1a27039a7d01102ae1291a373435..8cd7be567e6446b1ffa20a567e3e53a7ef54a610 100644 (file)
@@ -21,7 +21,11 @@ import bpy
 def init():
     import libcycles_blender as lib
     import os.path
-    lib.init(os.path.dirname(__file__))
+
+    path = os.path.dirname(__file__)
+    user_path = os.path.dirname(os.path.abspath(bpy.utils.user_resource('CONFIG', '')))
+
+    lib.init(path, user_path)
 
 def create(engine, data, scene, region = 0, v3d = 0, rv3d = 0):
     import libcycles_blender as lib
index b40f5206feaf8941cfb5ee5a7961f90199355033..1026d420c0242a7c6699fc566712c8c01e3d5d21 100644 (file)
@@ -28,12 +28,12 @@ CCL_NAMESPACE_BEGIN
 
 static PyObject *init_func(PyObject *self, PyObject *args)
 {
-       const char *path;
+       const char *path, *user_path;
 
-       if(!PyArg_ParseTuple(args, "s", &path))
+       if(!PyArg_ParseTuple(args, "ss", &path, &user_path))
                return NULL;
        
-       path_init(path);
+       path_init(path, user_path);
 
        Py_INCREF(Py_None);
        return Py_None;
index a6a66873b92cf4b4c93bdb5f30b1a727c5047d99..49ffd3d0834ed2099c0825ea1d6c16555abb26b1 100644 (file)
@@ -28,7 +28,9 @@
 #include "util_map.h"
 #include "util_opengl.h"
 #include "util_path.h"
+#include "util_system.h"
 #include "util_types.h"
+#include "util_time.h"
 
 CCL_NAMESPACE_BEGIN
 
@@ -125,6 +127,15 @@ public:
                } \
        }
 
+       bool cuda_error(CUresult result)
+       {
+               if(result == CUDA_SUCCESS)
+                       return false;
+
+               fprintf(stderr, "CUDA error: %s\n", cuda_error_string(result));
+               return true;
+       }
+
        void cuda_push_context()
        {
                cuda_assert(cuCtxSetCurrent(cuContext))
@@ -140,17 +151,26 @@ public:
                background = background_;
 
                cuDevId = 0;
+               cuDevice = 0;
+               cuContext = 0;
 
                /* intialize */
-               cuda_assert(cuInit(0))
+               if(cuda_error(cuInit(0)))
+                       return;
 
                /* setup device and context */
-               cuda_assert(cuDeviceGet(&cuDevice, cuDevId))
+               if(cuda_error(cuDeviceGet(&cuDevice, cuDevId)))
+                       return;
+
+               CUresult result;
 
                if(background)
-                       cuda_assert(cuCtxCreate(&cuContext, 0, cuDevice))
+                       result = cuCtxCreate(&cuContext, 0, cuDevice);
                else
-                       cuda_assert(cuGLCtxCreate(&cuContext, 0, cuDevice))
+                       result = cuGLCtxCreate(&cuContext, 0, cuDevice);
+
+               if(cuda_error(result))
+                       return;
 
                cuda_pop_context();
        }
@@ -173,21 +193,80 @@ public:
                return string("CUDA ") + deviceName;
        }
 
+       string compile_kernel()
+       {
+               /* compute cubin name */
+               int major, minor;
+               cuDeviceComputeCapability(&major, &minor, cuDevId);
+
+               /* attempt to use kernel provided with blender */
+               string cubin = path_get(string_printf("lib/kernel_sm_%d%d.cubin", major, minor));
+               if(path_exists(cubin))
+                       return cubin;
+
+               /* not found, try to use locally compiled kernel */
+               string kernel_path = path_get("kernel");
+               string md5 = path_files_md5_hash(kernel_path);
+
+               cubin = string_printf("cycles_kernel_sm%d%d_%s.cubin", major, minor, md5.c_str());;
+               cubin = path_user_get(path_join("cache", cubin));
+
+               /* if exists already, use it */
+               if(path_exists(cubin))
+                       return cubin;
+
+               /* if not, find CUDA compiler */
+               string nvcc = cuCompilerPath();
+
+               if(nvcc == "") {
+                       fprintf(stderr, "CUDA nvcc compiler not found. Install CUDA toolkit in default location.\n");
+                       return "";
+               }
+
+               /* compile */
+               string kernel = path_join(kernel_path, "kernel.cu");
+               string include = kernel_path;
+               const int machine = system_cpu_bits();
+               const int maxreg = 24;
+
+               double starttime = time_dt();
+               printf("Compiling CUDA kernel ...\n");
+
+               string command = string_printf("%s -arch=sm_%d%d -m%d --cubin \"%s\" --use_fast_math "
+                       "-o \"%s\" --ptxas-options=\"-v\" --maxrregcount=%d --opencc-options -OPT:Olimit=0 -I\"%s\" -DNVCC",
+                       nvcc.c_str(), major, minor, machine, kernel.c_str(), cubin.c_str(), maxreg, include.c_str());
+
+               system(command.c_str());
+
+               /* verify if compilation succeeded */
+               if(!path_exists(cubin)) {
+                       fprintf(stderr, "CUDA kernel compilation failed.\n");
+                       return "";
+               }
+
+               printf("Kernel compilation finished in %.2lfs.\n", time_dt() - starttime);
+
+               return cubin;
+       }
 
        bool load_kernels()
        {
-               CUresult result;
-               int major, minor;
+               /* check if cuda init succeeded */
+               if(cuContext == 0)
+                       return false;
 
-               cuda_push_context();
+               /* get kernel */
+               string cubin = compile_kernel();
+
+               if(cubin == "")
+                       return false;
 
                /* open module */
-               cuDeviceComputeCapability(&major, &minor, cuDevId);
-               string cubin = path_get(string_printf("lib/kernel_sm_%d%d.cubin", major, minor));
+               cuda_push_context();
 
-               result = cuModuleLoad(&cuModule, cubin.c_str());
-               if(result != CUDA_SUCCESS)
-                       fprintf(stderr, "Failed loading CUDA kernel %s (%s).\n", cubin.c_str(), cuda_error_string(result));
+               CUresult result = cuModuleLoad(&cuModule, cubin.c_str());
+               if(cuda_error(result))
+                       fprintf(stderr, "Failed loading CUDA kernel %s.\n", cubin.c_str());
 
                cuda_pop_context();
 
index c142701c873f4853ab9faa0326f56341f68893ee..a41238ffe0931d57f4424cbf7853ef3b5cd1eeb7 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "util_map.h"
 #include "util_math.h"
+#include "util_md5.h"
 #include "util_opencl.h"
 #include "util_opengl.h"
 #include "util_path.h"
@@ -118,7 +119,7 @@ public:
        void opencl_assert(cl_int err)
        {
                if(err != CL_SUCCESS) {
-                       printf("error (%d): %s\n", err, opencl_error_string(err));
+                       fprintf(stderr, "OpenCL error (%d): %s\n", err, opencl_error_string(err));
 #ifndef NDEBUG
                        abort();
 #endif
@@ -157,7 +158,7 @@ public:
 
                cpPlatform = platform_ids[0]; /* todo: pick specified platform && device */
 
-               ciErr = clGetDeviceIDs(cpPlatform, CL_DEVICE_TYPE_ALL, 1, &cdDevice, NULL);
+               ciErr = clGetDeviceIDs(cpPlatform, CL_DEVICE_TYPE_GPU|CL_DEVICE_TYPE_ACCELERATOR, 1, &cdDevice, NULL);
                if(opencl_error(ciErr))
                        return;
 
@@ -208,38 +209,67 @@ public:
                return true;
        }
 
-       bool load_kernels()
+       bool load_binary(const string& kernel_path, const string& clbin)
        {
-               /* verify if device was initialized */
-               if(!device_initialized)
+               /* read binary into memory */
+               vector<uint8_t> binary;
+
+               if(!path_read_binary(clbin, binary)) {
+                       fprintf(stderr, "OpenCL failed to read cached binary %s.\n", clbin.c_str());
                        return false;
+               }
 
-               /* verify we have right opencl version */
-               if(!opencl_version_check())
+               /* create program */
+               cl_int status;
+               size_t size = binary.size();
+               const uint8_t *bytes = &binary[0];
+
+               cpProgram = clCreateProgramWithBinary(cxContext, 1, &cdDevice,
+                       &size, &bytes, &status, &ciErr);
+
+               if(opencl_error(status) || opencl_error(ciErr)) {
+                       fprintf(stderr, "OpenCL failed create program from cached binary %s.\n", clbin.c_str());
                        return false;
+               }
 
-               /* we compile kernels consisting of many files. unfortunately opencl
-                  kernel caches do not seem to recognize changes in included files.
-                  so we force recompile on changes by adding the md5 hash of all files */
-               string kernel_path = path_get("kernel");
-               string kernel_md5 = path_files_md5_hash(kernel_path);
+               if(!build_kernel(kernel_path))
+                       return false;
 
-               string source = "#include \"kernel.cl\" // " + kernel_md5 + "\n";
-               size_t source_len = source.size();
-               const char *source_str = source.c_str();
+               return true;
+       }
+
+       bool save_binary(const string& clbin)
+       {
+               size_t size = 0;
+               clGetProgramInfo(cpProgram, CL_PROGRAM_BINARY_SIZES, sizeof(size_t), &size, NULL);
+
+               if(!size)
+                       return false;
+
+               vector<uint8_t> binary(size);
+               uint8_t *bytes = &binary[0];
+
+               clGetProgramInfo(cpProgram, CL_PROGRAM_BINARIES, sizeof(uint8_t*), &bytes, NULL);
+
+               if(!path_write_binary(clbin, binary)) {
+                       fprintf(stderr, "OpenCL failed to write cached binary %s.\n", clbin.c_str());
+                       return false;
+               }
+
+               return true;
+       }
 
+       bool build_kernel(const string& kernel_path)
+       {
                string build_options = "";
 
                build_options += "-I " + kernel_path + ""; /* todo: escape path */
                build_options += " -cl-fast-relaxed-math -cl-strict-aliasing";
 
-               cpProgram = clCreateProgramWithSource(cxContext, 1, &source_str, &source_len, &ciErr);
-               if(opencl_error(ciErr))
-                       return false;
-
                ciErr = clBuildProgram(cpProgram, 0, NULL, build_options.c_str(), NULL, NULL);
 
                if(ciErr != CL_SUCCESS) {
+                       /* show build errors */
                        char *build_log;
                        size_t ret_val_size;
 
@@ -256,6 +286,87 @@ public:
                        return false;
                }
 
+               return true;
+       }
+
+       bool compile_kernel(const string& kernel_path, const string& kernel_md5)
+       {
+               /* we compile kernels consisting of many files. unfortunately opencl
+                  kernel caches do not seem to recognize changes in included files.
+                  so we force recompile on changes by adding the md5 hash of all files */
+               string source = "#include \"kernel.cl\" // " + kernel_md5 + "\n";
+               size_t source_len = source.size();
+               const char *source_str = source.c_str();
+
+               cpProgram = clCreateProgramWithSource(cxContext, 1, &source_str, &source_len, &ciErr);
+
+               if(opencl_error(ciErr))
+                       return false;
+
+               double starttime = time_dt();
+               printf("Compiling OpenCL kernel ...\n");
+
+               if(!build_kernel(kernel_path))
+                       return false;
+
+               printf("Kernel compilation finished in %.2lfs.\n", time_dt() - starttime);
+
+               return true;
+       }
+
+       string device_md5_hash()
+       {
+               MD5Hash md5;
+               char version[256], driver[256], name[256], vendor[256];
+
+               clGetPlatformInfo(cpPlatform, CL_PLATFORM_VENDOR, sizeof(vendor), &vendor, NULL);
+               clGetDeviceInfo(cdDevice, CL_DEVICE_VERSION, sizeof(version), &version, NULL);
+               clGetDeviceInfo(cdDevice, CL_DEVICE_NAME, sizeof(name), &name, NULL);
+               clGetDeviceInfo(cdDevice, CL_DRIVER_VERSION, sizeof(driver), &driver, NULL);
+
+               md5.append((uint8_t*)vendor, strlen(vendor));
+               md5.append((uint8_t*)version, strlen(version));
+               md5.append((uint8_t*)name, strlen(name));
+               md5.append((uint8_t*)driver, strlen(driver));
+
+               return md5.get_hex();
+       }
+
+       bool load_kernels()
+       {
+               /* verify if device was initialized */
+               if(!device_initialized) {
+                       fprintf(stderr, "OpenCL: failed to initialize device.\n");
+                       return false;
+               }
+
+               /* verify we have right opencl version */
+               if(!opencl_version_check())
+                       return false;
+
+               /* md5 hash to detect changes */
+               string kernel_path = path_get("kernel");
+               string kernel_md5 = path_files_md5_hash(kernel_path);
+               string device_md5 = device_md5_hash();
+
+               /* try to use cache binary */
+               string clbin = string_printf("cycles_kernel_%s_%s.clbin", device_md5.c_str(), kernel_md5.c_str());;
+               clbin = path_user_get(path_join("cache", clbin));
+
+               if(path_exists(clbin)) {
+                       /* if exists already, try use it */
+                       if(!load_binary(kernel_path, clbin))
+                               return false;
+               }
+               else {
+                       /* compile kernel */
+                       if(!compile_kernel(kernel_path, kernel_md5))
+                               return false;
+
+                       /* save binary for reuse */
+                       save_binary(clbin);
+               }
+
                /* find kernels */
                ckPathTraceKernel = clCreateKernel(cpProgram, "kernel_ocl_path_trace", &ciErr);
                if(opencl_error(ciErr))
index 31fe6994a1aec453bf043075020461ac208a82ac..9714f94108205d6c775895b7cceb8bdd8d98fb0d 100644 (file)
@@ -132,6 +132,7 @@ endif()
 #delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${kernel_preprocessed}" ${CYCLES_INSTALL_PATH}/kernel)
 
 delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "kernel.cl" ${CYCLES_INSTALL_PATH}/kernel)
+delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "kernel.cu" ${CYCLES_INSTALL_PATH}/kernel)
 delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${headers}" ${CYCLES_INSTALL_PATH}/kernel)
 delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${svm_headers}" ${CYCLES_INSTALL_PATH}/kernel/svm)
 delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${util_headers}" ${CYCLES_INSTALL_PATH}/kernel)
index 3a0eff5210c1af2cd5779e7b68579196c6c58648..72aef463cab125f38d8d273514919015bb0457e2 100644 (file)
 
 #define __KERNEL_GPU__
 #define __KERNEL_CUDA__
+#define CCL_NAMESPACE_BEGIN
+#define CCL_NAMESPACE_END
 
 #include <cuda.h>
 #include <float.h>
 
 #include "util_types.h"
 
-CCL_NAMESPACE_BEGIN
-
 /* Qualifier wrappers for different names on different devices */
 
 #define __device  __device__ __inline__
@@ -60,7 +60,5 @@ typedef texture<uchar4, 2, cudaReadModeNormalizedFloat> texture_image_uchar4;
 
 #define kernel_data __data
 
-CCL_NAMESPACE_END
-
 #endif /* __KERNEL_COMPAT_CUDA_H__ */
 
index f66c4a64b0b86bc72f5c88706ff32b26b0581f36..5fb687971ef1da79b24d6f04213d4a2e42cf4893 100644 (file)
@@ -384,7 +384,7 @@ void Session::run_cpu()
 void Session::run()
 {
        /* load kernels */
-       progress.set_status("Loading render kernels (may take a few minutes)");
+       progress.set_status("Loading render kernels (may take a few minutes the first time)");
 
        if(!device->load_kernels()) {
                progress.set_status("Failed loading render kernel, see console for errors");
index b371f4e9499735020695345a50ca11f16d72b448..14e3f9a159bd2e49abc4a5602af623e418861e68 100644 (file)
@@ -16,6 +16,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
+#include <stdlib.h>
+
 #include "util_cuda.h"
 #include "util_debug.h"
 #include "util_dynlib.h"
@@ -371,6 +373,11 @@ bool cuLibraryInit()
        /* cuda 4.0 */
        CUDA_LIBRARY_FIND(cuCtxSetCurrent);
 
+#ifndef WITH_CUDA_BINARIES
+       if(cuCompilerPath() == "")
+               return false;
+#endif
+
        /* success */
        result = true;
 
@@ -379,13 +386,23 @@ bool cuLibraryInit()
 
 string cuCompilerPath()
 {
-               /* todo: better nvcc detection */
 #ifdef _WIN32
-       string nvcc = "C:/CUDA/bin/nvcc.exe";
+       const char *defaultpath = "C:/CUDA/bin";
+       const char *executable = "nvcc.exe";
 #else
-       string nvcc = "/usr/local/cuda/bin/nvcc";
+       const char *defaultpath = "/usr/local/cuda/bin";
+       const char *executable = "nvcc";
 #endif
 
+       const char *binpath = getenv("CUDA_BIN_PATH");
+
+       string nvcc;
+
+       if(binpath)
+               nvcc = path_join(binpath, executable);
+       else
+               nvcc = path_join(defaultpath, executable);
+
        return (path_exists(nvcc))? nvcc: "";
 }
 
index 086063bcb810b1dfb2b36536c6fffb5053c54332..90093e32d58d30cdc0fcb48362c5a974562b921d 100644 (file)
@@ -24,6 +24,8 @@
 #include <OpenImageIO/sysutil.h>
 OIIO_NAMESPACE_USING
 
+#include <stdio.h>
+
 #define BOOST_FILESYSTEM_VERSION 2
 
 #include <boost/filesystem.hpp> 
@@ -32,10 +34,12 @@ OIIO_NAMESPACE_USING
 CCL_NAMESPACE_BEGIN
 
 static string cached_path = "";
+static string cached_user_path = "";
 
-void path_init(const string& path)
+void path_init(const string& path, const string& user_path)
 {
        cached_path = path;
+       cached_user_path = user_path;
 }
 
 string path_get(const string& sub)
@@ -46,6 +50,14 @@ string path_get(const string& sub)
        return path_join(cached_path, sub);
 }
 
+string path_user_get(const string& sub)
+{
+       if(cached_user_path == "")
+               cached_user_path = path_dirname(Sysutil::this_program_path());
+
+       return path_join(cached_user_path, sub);
+}
+
 string path_filename(const string& path)
 {
        return boost::filesystem::path(path).filename();
@@ -97,5 +109,48 @@ string path_files_md5_hash(const string& dir)
        return hash.get_hex();
 }
 
+bool path_write_binary(const string& path, const vector<uint8_t>& binary)
+{
+       /* write binary file from memory */
+       boost::filesystem::create_directories(path_dirname(path));
+
+       FILE *f = fopen(path.c_str(), "wb");
+
+       if(!f)
+               return false;
+
+       if(binary.size() > 0)
+               fwrite(&binary[0], sizeof(uint8_t), binary.size(), f);
+
+       fclose(f);
+
+       return true;
+}
+
+bool path_read_binary(const string& path, vector<uint8_t>& binary)
+{
+       binary.resize(boost::filesystem::file_size(path));
+
+       /* read binary file into memory */
+       FILE *f = fopen(path.c_str(), "rb");
+
+       if(!f)
+               return false;
+
+       if(binary.size() == 0) {
+               fclose(f);
+               return false;
+       }
+
+       if(fread(&binary[0], sizeof(uint8_t), binary.size(), f) != binary.size()) {
+               fclose(f);
+               return false;
+       }
+
+       fclose(f);
+
+       return true;
+}
+
 CCL_NAMESPACE_END
 
index 3e07f9524bf6676fd34eed3291220aee7a4b4ee8..ec3524d362a5505d392724c8120cb18b8b3d926b 100644 (file)
  * then makes all paths relative to that. */
 
 #include "util_string.h"
+#include "util_vector.h"
 
 CCL_NAMESPACE_BEGIN
 
-void path_init(const string& path = "");
+void path_init(const string& path = "", const string& user_path = "");
 string path_get(const string& sub = "");
+string path_user_get(const string& sub = "");
 
 string path_filename(const string& path);
 string path_dirname(const string& path);
@@ -39,6 +41,9 @@ string path_escape(const string& path);
 bool path_exists(const string& path);
 string path_files_md5_hash(const string& dir);
 
+bool path_write_binary(const string& path, const vector<uint8_t>& binary);
+bool path_read_binary(const string& path, vector<uint8_t>& binary);
+
 CCL_NAMESPACE_END
 
 #endif