diff options
Diffstat (limited to 'intern/cycles/device/device_opencl.cpp')
-rw-r--r-- | intern/cycles/device/device_opencl.cpp | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/intern/cycles/device/device_opencl.cpp b/intern/cycles/device/device_opencl.cpp new file mode 100644 index 00000000000..d8df8025a08 --- /dev/null +++ b/intern/cycles/device/device_opencl.cpp @@ -0,0 +1,648 @@ +/* + * Copyright 2011, Blender Foundation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef WITH_OPENCL + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "device.h" +#include "device_intern.h" + +#include "util_map.h" +#include "util_math.h" +#include "util_md5.h" +#include "util_opencl.h" +#include "util_opengl.h" +#include "util_path.h" +#include "util_time.h" + +CCL_NAMESPACE_BEGIN + +#define CL_MEM_PTR(p) ((cl_mem)(unsigned long)(p)) + +class OpenCLDevice : public Device +{ +public: + cl_context cxContext; + cl_command_queue cqCommandQueue; + cl_platform_id cpPlatform; + cl_device_id cdDevice; + cl_program cpProgram; + cl_kernel ckPathTraceKernel; + cl_kernel ckFilmConvertKernel; + cl_int ciErr; + map<string, device_vector<uchar>*> const_mem_map; + map<string, device_memory*> mem_map; + device_ptr null_mem; + bool device_initialized; + + const char *opencl_error_string(cl_int err) + { + switch (err) { + case CL_SUCCESS: return "Success!"; + case CL_DEVICE_NOT_FOUND: return "Device not found."; + case CL_DEVICE_NOT_AVAILABLE: return "Device not available"; + case CL_COMPILER_NOT_AVAILABLE: return "Compiler not available"; + case CL_MEM_OBJECT_ALLOCATION_FAILURE: return "Memory object allocation failure"; + case CL_OUT_OF_RESOURCES: return "Out of resources"; + case CL_OUT_OF_HOST_MEMORY: return "Out of host memory"; + case CL_PROFILING_INFO_NOT_AVAILABLE: return "Profiling information not available"; + case CL_MEM_COPY_OVERLAP: return "Memory copy overlap"; + case CL_IMAGE_FORMAT_MISMATCH: return "Image format mismatch"; + case CL_IMAGE_FORMAT_NOT_SUPPORTED: return "Image format not supported"; + case CL_BUILD_PROGRAM_FAILURE: return "Program build failure"; + case CL_MAP_FAILURE: return "Map failure"; + case CL_INVALID_VALUE: return "Invalid value"; + case CL_INVALID_DEVICE_TYPE: return "Invalid device type"; + case CL_INVALID_PLATFORM: return "Invalid platform"; + case CL_INVALID_DEVICE: return "Invalid device"; + case CL_INVALID_CONTEXT: return "Invalid context"; + case CL_INVALID_QUEUE_PROPERTIES: return "Invalid queue properties"; + case CL_INVALID_COMMAND_QUEUE: return "Invalid command queue"; + case CL_INVALID_HOST_PTR: return "Invalid host pointer"; + case CL_INVALID_MEM_OBJECT: return "Invalid memory object"; + case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: return "Invalid image format descriptor"; + case CL_INVALID_IMAGE_SIZE: return "Invalid image size"; + case CL_INVALID_SAMPLER: return "Invalid sampler"; + case CL_INVALID_BINARY: return "Invalid binary"; + case CL_INVALID_BUILD_OPTIONS: return "Invalid build options"; + case CL_INVALID_PROGRAM: return "Invalid program"; + case CL_INVALID_PROGRAM_EXECUTABLE: return "Invalid program executable"; + case CL_INVALID_KERNEL_NAME: return "Invalid kernel name"; + case CL_INVALID_KERNEL_DEFINITION: return "Invalid kernel definition"; + case CL_INVALID_KERNEL: return "Invalid kernel"; + case CL_INVALID_ARG_INDEX: return "Invalid argument index"; + case CL_INVALID_ARG_VALUE: return "Invalid argument value"; + case CL_INVALID_ARG_SIZE: return "Invalid argument size"; + case CL_INVALID_KERNEL_ARGS: return "Invalid kernel arguments"; + case CL_INVALID_WORK_DIMENSION: return "Invalid work dimension"; + case CL_INVALID_WORK_GROUP_SIZE: return "Invalid work group size"; + case CL_INVALID_WORK_ITEM_SIZE: return "Invalid work item size"; + case CL_INVALID_GLOBAL_OFFSET: return "Invalid global offset"; + case CL_INVALID_EVENT_WAIT_LIST: return "Invalid event wait list"; + case CL_INVALID_EVENT: return "Invalid event"; + case CL_INVALID_OPERATION: return "Invalid operation"; + case CL_INVALID_GL_OBJECT: return "Invalid OpenGL object"; + case CL_INVALID_BUFFER_SIZE: return "Invalid buffer size"; + case CL_INVALID_MIP_LEVEL: return "Invalid mip-map level"; + default: return "Unknown"; + } + } + + bool opencl_error(cl_int err) + { + if(err != CL_SUCCESS) { + fprintf(stderr, "OpenCL error (%d): %s\n", err, opencl_error_string(err)); + return true; + } + + return false; + } + + void opencl_assert(cl_int err) + { + if(err != CL_SUCCESS) { + fprintf(stderr, "OpenCL error (%d): %s\n", err, opencl_error_string(err)); +#ifndef NDEBUG + abort(); +#endif + } + } + + OpenCLDevice(bool background_) + { + background = background_; + cpPlatform = NULL; + cxContext = NULL; + cqCommandQueue = NULL; + cpProgram = NULL; + ckPathTraceKernel = NULL; + ckFilmConvertKernel = NULL; + null_mem = 0; + device_initialized = false; + + vector<cl_platform_id> platform_ids; + cl_uint num_platforms; + + /* setup device */ + ciErr = clGetPlatformIDs(0, NULL, &num_platforms); + if(opencl_error(ciErr)) + return; + + if(num_platforms == 0) { + fprintf(stderr, "OpenCL: no platforms found.\n"); + return; + } + + platform_ids.resize(num_platforms); + ciErr = clGetPlatformIDs(num_platforms, &platform_ids[0], NULL); + if(opencl_error(ciErr)) + return; + + cpPlatform = platform_ids[0]; /* todo: pick specified platform && device */ + + ciErr = clGetDeviceIDs(cpPlatform, CL_DEVICE_TYPE_GPU|CL_DEVICE_TYPE_ACCELERATOR, 1, &cdDevice, NULL); + if(opencl_error(ciErr)) + return; + + cxContext = clCreateContext(0, 1, &cdDevice, NULL, NULL, &ciErr); + if(opencl_error(ciErr)) + return; + + cqCommandQueue = clCreateCommandQueue(cxContext, cdDevice, 0, &ciErr); + if(opencl_error(ciErr)) + return; + + null_mem = (device_ptr)clCreateBuffer(cxContext, CL_MEM_READ_ONLY, 1, NULL, &ciErr); + device_initialized = true; + } + + bool opencl_version_check() + { + char version[256]; + int major, minor, req_major = 1, req_minor = 1; + + clGetPlatformInfo(cpPlatform, CL_PLATFORM_VERSION, sizeof(version), &version, NULL); + + if(sscanf(version, "OpenCL %d.%d", &major, &minor) < 2) { + fprintf(stderr, "OpenCL: failed to parse platform version string (%s).", version); + return false; + } + + if(!((major == req_major && minor >= req_minor) || (major > req_major))) { + fprintf(stderr, "OpenCL: platform version 1.1 or later required, found %d.%d\n", major, minor); + return false; + } + + clGetDeviceInfo(cdDevice, CL_DEVICE_OPENCL_C_VERSION, sizeof(version), &version, NULL); + + if(sscanf(version, "OpenCL C %d.%d", &major, &minor) < 2) { + fprintf(stderr, "OpenCL: failed to parse OpenCL C version string (%s).", version); + return false; + } + + if(!((major == req_major && minor >= req_minor) || (major > req_major))) { + fprintf(stderr, "OpenCL: C version 1.1 or later required, found %d.%d\n", major, minor); + return false; + } + + /* we don't check CL_DEVICE_VERSION since for e.g. nvidia sm 1.3 cards this is + 1.0 even if the language features are there, just limited shared memory */ + + return true; + } + + bool load_binary(const string& kernel_path, const string& clbin) + { + /* 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; + } + + /* 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; + } + + if(!build_kernel(kernel_path)) + return false; + + 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 "; + + 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; + + clGetProgramBuildInfo(cpProgram, cdDevice, CL_PROGRAM_BUILD_LOG, 0, NULL, &ret_val_size); + + build_log = new char[ret_val_size+1]; + clGetProgramBuildInfo(cpProgram, cdDevice, CL_PROGRAM_BUILD_LOG, ret_val_size, build_log, NULL); + + build_log[ret_val_size] = '\0'; + fprintf(stderr, "OpenCL build failed:\n %s\n", build_log); + + delete[] build_log; + + 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)) + return false; + + ckFilmConvertKernel = clCreateKernel(cpProgram, "kernel_ocl_tonemap", &ciErr); + if(opencl_error(ciErr)) + return false; + + return true; + } + + ~OpenCLDevice() + { + if(null_mem) + clReleaseMemObject(CL_MEM_PTR(null_mem)); + + map<string, device_vector<uchar>*>::iterator mt; + for(mt = const_mem_map.begin(); mt != const_mem_map.end(); mt++) { + mem_free(*(mt->second)); + delete mt->second; + } + + if(ckPathTraceKernel) + clReleaseKernel(ckPathTraceKernel); + if(ckFilmConvertKernel) + clReleaseKernel(ckFilmConvertKernel); + if(cpProgram) + clReleaseProgram(cpProgram); + if(cqCommandQueue) + clReleaseCommandQueue(cqCommandQueue); + if(cxContext) + clReleaseContext(cxContext); + } + + bool support_full_kernel() + { + return false; + } + + string description() + { + char name[1024]; + + clGetDeviceInfo(cdDevice, CL_DEVICE_NAME, sizeof(name), &name, NULL); + + return string("OpenCL ") + name; + } + + void mem_alloc(device_memory& mem, MemoryType type) + { + size_t size = mem.memory_size(); + + if(type == MEM_READ_ONLY) + mem.device_pointer = (device_ptr)clCreateBuffer(cxContext, CL_MEM_READ_ONLY, size, NULL, &ciErr); + else if(type == MEM_WRITE_ONLY) + mem.device_pointer = (device_ptr)clCreateBuffer(cxContext, CL_MEM_WRITE_ONLY, size, NULL, &ciErr); + else + mem.device_pointer = (device_ptr)clCreateBuffer(cxContext, CL_MEM_READ_WRITE, size, NULL, &ciErr); + + opencl_assert(ciErr); + } + + void mem_copy_to(device_memory& mem) + { + /* this is blocking */ + size_t size = mem.memory_size(); + ciErr = clEnqueueWriteBuffer(cqCommandQueue, CL_MEM_PTR(mem.device_pointer), CL_TRUE, 0, size, (void*)mem.data_pointer, 0, NULL, NULL); + opencl_assert(ciErr); + } + + void mem_copy_from(device_memory& mem, size_t offset, size_t size) + { + ciErr = clEnqueueReadBuffer(cqCommandQueue, CL_MEM_PTR(mem.device_pointer), CL_TRUE, offset, size, (uchar*)mem.data_pointer + offset, 0, NULL, NULL); + opencl_assert(ciErr); + } + + void mem_zero(device_memory& mem) + { + if(mem.device_pointer) { + memset((void*)mem.data_pointer, 0, mem.memory_size()); + mem_copy_to(mem); + } + } + + void mem_free(device_memory& mem) + { + if(mem.device_pointer) { + ciErr = clReleaseMemObject(CL_MEM_PTR(mem.device_pointer)); + mem.device_pointer = 0; + opencl_assert(ciErr); + } + } + + void const_copy_to(const char *name, void *host, size_t size) + { + if(const_mem_map.find(name) == const_mem_map.end()) { + device_vector<uchar> *data = new device_vector<uchar>(); + data->copy((uchar*)host, size); + + mem_alloc(*data, MEM_READ_ONLY); + const_mem_map[name] = data; + } + else { + device_vector<uchar> *data = const_mem_map[name]; + data->copy((uchar*)host, size); + } + + mem_copy_to(*const_mem_map[name]); + } + + void tex_alloc(const char *name, device_memory& mem, bool interpolation, bool periodic) + { + mem_alloc(mem, MEM_READ_ONLY); + mem_copy_to(mem); + mem_map[name] = &mem; + } + + void tex_free(device_memory& mem) + { + if(mem.data_pointer) + mem_free(mem); + } + + size_t global_size_round_up(int group_size, int global_size) + { + int r = global_size % group_size; + return global_size + ((r == 0)? 0: group_size - r); + } + + void path_trace(DeviceTask& task) + { + /* cast arguments to cl types */ + cl_mem d_data = CL_MEM_PTR(const_mem_map["__data"]->device_pointer); + cl_mem d_buffer = CL_MEM_PTR(task.buffer); + cl_mem d_rng_state = CL_MEM_PTR(task.rng_state); + cl_int d_x = task.x; + cl_int d_y = task.y; + cl_int d_w = task.w; + cl_int d_h = task.h; + cl_int d_sample = task.sample; + + /* sample arguments */ + int narg = 0; + ciErr = 0; + + ciErr |= clSetKernelArg(ckPathTraceKernel, narg++, sizeof(d_data), (void*)&d_data); + ciErr |= clSetKernelArg(ckPathTraceKernel, narg++, sizeof(d_buffer), (void*)&d_buffer); + ciErr |= clSetKernelArg(ckPathTraceKernel, narg++, sizeof(d_rng_state), (void*)&d_rng_state); + +#define KERNEL_TEX(type, ttype, name) \ + ciErr |= set_kernel_arg_mem(ckPathTraceKernel, &narg, #name); +#include "kernel_textures.h" + + ciErr |= clSetKernelArg(ckPathTraceKernel, narg++, sizeof(d_sample), (void*)&d_sample); + ciErr |= clSetKernelArg(ckPathTraceKernel, narg++, sizeof(d_x), (void*)&d_x); + ciErr |= clSetKernelArg(ckPathTraceKernel, narg++, sizeof(d_y), (void*)&d_y); + ciErr |= clSetKernelArg(ckPathTraceKernel, narg++, sizeof(d_w), (void*)&d_w); + ciErr |= clSetKernelArg(ckPathTraceKernel, narg++, sizeof(d_h), (void*)&d_h); + + opencl_assert(ciErr); + + size_t workgroup_size; + + clGetKernelWorkGroupInfo(ckPathTraceKernel, cdDevice, + CL_KERNEL_WORK_GROUP_SIZE, sizeof(size_t), &workgroup_size, NULL); + + workgroup_size = max(sqrt((double)workgroup_size), 1.0); + + size_t local_size[2] = {workgroup_size, workgroup_size}; + size_t global_size[2] = {global_size_round_up(local_size[0], d_w), global_size_round_up(local_size[1], d_h)}; + + /* run kernel */ + ciErr = clEnqueueNDRangeKernel(cqCommandQueue, ckPathTraceKernel, 2, NULL, global_size, local_size, 0, NULL, NULL); + opencl_assert(ciErr); + opencl_assert(clFinish(cqCommandQueue)); + } + + cl_int set_kernel_arg_mem(cl_kernel kernel, int *narg, const char *name) + { + cl_mem ptr; + cl_int size, err = 0; + + if(mem_map.find(name) != mem_map.end()) { + device_memory *mem = mem_map[name]; + + ptr = CL_MEM_PTR(mem->device_pointer); + size = mem->data_width; + } + else { + /* work around NULL not working, even though the spec says otherwise */ + ptr = CL_MEM_PTR(null_mem); + size = 1; + } + + err |= clSetKernelArg(kernel, (*narg)++, sizeof(ptr), (void*)&ptr); + opencl_assert(err); + err |= clSetKernelArg(kernel, (*narg)++, sizeof(size), (void*)&size); + opencl_assert(err); + + return err; + } + + void tonemap(DeviceTask& task) + { + /* cast arguments to cl types */ + cl_mem d_data = CL_MEM_PTR(const_mem_map["__data"]->device_pointer); + cl_mem d_rgba = CL_MEM_PTR(task.rgba); + cl_mem d_buffer = CL_MEM_PTR(task.buffer); + cl_int d_x = task.x; + cl_int d_y = task.y; + cl_int d_w = task.w; + cl_int d_h = task.h; + cl_int d_sample = task.sample; + cl_int d_resolution = task.resolution; + + /* sample arguments */ + int narg = 0; + ciErr = 0; + + ciErr |= clSetKernelArg(ckFilmConvertKernel, narg++, sizeof(d_data), (void*)&d_data); + ciErr |= clSetKernelArg(ckFilmConvertKernel, narg++, sizeof(d_rgba), (void*)&d_rgba); + ciErr |= clSetKernelArg(ckFilmConvertKernel, narg++, sizeof(d_buffer), (void*)&d_buffer); + +#define KERNEL_TEX(type, ttype, name) \ + ciErr |= set_kernel_arg_mem(ckFilmConvertKernel, &narg, #name); +#include "kernel_textures.h" + + ciErr |= clSetKernelArg(ckFilmConvertKernel, narg++, sizeof(d_sample), (void*)&d_sample); + ciErr |= clSetKernelArg(ckFilmConvertKernel, narg++, sizeof(d_resolution), (void*)&d_resolution); + ciErr |= clSetKernelArg(ckFilmConvertKernel, narg++, sizeof(d_x), (void*)&d_x); + ciErr |= clSetKernelArg(ckFilmConvertKernel, narg++, sizeof(d_y), (void*)&d_y); + ciErr |= clSetKernelArg(ckFilmConvertKernel, narg++, sizeof(d_w), (void*)&d_w); + ciErr |= clSetKernelArg(ckFilmConvertKernel, narg++, sizeof(d_h), (void*)&d_h); + + opencl_assert(ciErr); + + size_t workgroup_size; + + clGetKernelWorkGroupInfo(ckFilmConvertKernel, cdDevice, + CL_KERNEL_WORK_GROUP_SIZE, sizeof(size_t), &workgroup_size, NULL); + + workgroup_size = max(sqrt((double)workgroup_size), 1.0); + + size_t local_size[2] = {workgroup_size, workgroup_size}; + size_t global_size[2] = {global_size_round_up(local_size[0], d_w), global_size_round_up(local_size[1], d_h)}; + + /* run kernel */ + ciErr = clEnqueueNDRangeKernel(cqCommandQueue, ckFilmConvertKernel, 2, NULL, global_size, local_size, 0, NULL, NULL); + opencl_assert(ciErr); + opencl_assert(clFinish(cqCommandQueue)); + } + + void task_add(DeviceTask& task) + { + if(task.type == DeviceTask::TONEMAP) + tonemap(task); + else if(task.type == DeviceTask::PATH_TRACE) + path_trace(task); + } + + void task_wait() + { + } + + void task_cancel() + { + } +}; + +Device *device_opencl_create(bool background) +{ + return new OpenCLDevice(background); +} + +CCL_NAMESPACE_END + +#endif /* WITH_OPENCL */ + |