/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % OOO PPPP EEEEE N N CCCC L % % O O P P E NN N C L % % O O PPPP EEE N N N C L % % O O P E N NN C L % % OOO P EEEEE N N CCCC LLLLL % % % % % % MagickCore OpenCL Methods % % % % Software Design % % Cristy % % March 2000 % % % % % % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization % % dedicated to making software imaging solutions freely available. % % % % You may not use this file except in compliance with the License. You may % % obtain a copy of the License at % % % % https://imagemagick.org/script/license.php % % % % Unless required by applicable law or agreed to in writing, software % % distributed under the License is distributed on an "AS IS" BASIS, % % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % % See the License for the specific language governing permissions and % % limitations under the License. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % */ /* Include declarations. */ #include "MagickCore/studio.h" #include "MagickCore/artifact.h" #include "MagickCore/cache.h" #include "MagickCore/cache-private.h" #include "MagickCore/color.h" #include "MagickCore/compare.h" #include "MagickCore/constitute.h" #include "MagickCore/configure.h" #include "MagickCore/distort.h" #include "MagickCore/draw.h" #include "MagickCore/effect.h" #include "MagickCore/exception.h" #include "MagickCore/exception-private.h" #include "MagickCore/fx.h" #include "MagickCore/gem.h" #include "MagickCore/geometry.h" #include "MagickCore/image.h" #include "MagickCore/image-private.h" #include "MagickCore/layer.h" #include "MagickCore/mime-private.h" #include "MagickCore/memory_.h" #include "MagickCore/memory-private.h" #include "MagickCore/monitor.h" #include "MagickCore/montage.h" #include "MagickCore/morphology.h" #include "MagickCore/nt-base.h" #include "MagickCore/nt-base-private.h" #include "MagickCore/opencl.h" #include "MagickCore/opencl-private.h" #include "MagickCore/option.h" #include "MagickCore/policy.h" #include "MagickCore/property.h" #include "MagickCore/quantize.h" #include "MagickCore/quantum.h" #include "MagickCore/random_.h" #include "MagickCore/random-private.h" #include "MagickCore/resample.h" #include "MagickCore/resource_.h" #include "MagickCore/splay-tree.h" #include "MagickCore/semaphore.h" #include "MagickCore/statistic.h" #include "MagickCore/string_.h" #include "MagickCore/string-private.h" #include "MagickCore/token.h" #include "MagickCore/utility.h" #include "MagickCore/utility-private.h" #if defined(MAGICKCORE_OPENCL_SUPPORT) #if defined(MAGICKCORE_LTDL_DELEGATE) #include "ltdl.h" #endif #ifndef MAGICKCORE_WINDOWS_SUPPORT #include #endif #ifdef MAGICKCORE_HAVE_OPENCL_CL_H #define MAGICKCORE_OPENCL_MACOSX 1 #endif /* Define declarations. */ #define IMAGEMAGICK_PROFILE_FILE "ImagemagickOpenCLDeviceProfile.xml" /* Typedef declarations. */ typedef struct { long long freq; long long clocks; long long start; } AccelerateTimer; typedef struct { char *name, *platform_name, *vendor_name, *version; cl_uint max_clock_frequency, max_compute_units; double score; } MagickCLDeviceBenchmark; /* Forward declarations. */ static MagickBooleanType HasOpenCLDevices(MagickCLEnv,ExceptionInfo *), LoadOpenCLLibrary(void); static MagickCLDevice RelinquishMagickCLDevice(MagickCLDevice); static MagickCLEnv RelinquishMagickCLEnv(MagickCLEnv); static void BenchmarkOpenCLDevices(MagickCLEnv); extern const char *accelerateKernels, *accelerateKernels2; /* OpenCL library */ MagickLibrary *openCL_library; /* Default OpenCL environment */ MagickCLEnv default_CLEnv; MagickThreadType test_thread_id=0; SemaphoreInfo *openCL_lock; /* Cached location of the OpenCL cache files */ char *cache_directory; SemaphoreInfo *cache_directory_lock; static inline MagickBooleanType IsSameOpenCLDevice(MagickCLDevice a, MagickCLDevice b) { if ((LocaleCompare(a->platform_name,b->platform_name) == 0) && (LocaleCompare(a->vendor_name,b->vendor_name) == 0) && (LocaleCompare(a->name,b->name) == 0) && (LocaleCompare(a->version,b->version) == 0) && (a->max_clock_frequency == b->max_clock_frequency) && (a->max_compute_units == b->max_compute_units)) return(MagickTrue); return(MagickFalse); } static inline MagickBooleanType IsBenchmarkedOpenCLDevice(MagickCLDevice a, MagickCLDeviceBenchmark *b) { if ((LocaleCompare(a->platform_name,b->platform_name) == 0) && (LocaleCompare(a->vendor_name,b->vendor_name) == 0) && (LocaleCompare(a->name,b->name) == 0) && (LocaleCompare(a->version,b->version) == 0) && (a->max_clock_frequency == b->max_clock_frequency) && (a->max_compute_units == b->max_compute_units)) return(MagickTrue); return(MagickFalse); } static inline void RelinquishMagickCLDevices(MagickCLEnv clEnv) { size_t i; if (clEnv->devices != (MagickCLDevice *) NULL) { for (i = 0; i < clEnv->number_devices; i++) clEnv->devices[i]=RelinquishMagickCLDevice(clEnv->devices[i]); clEnv->devices=(MagickCLDevice *) RelinquishMagickMemory(clEnv->devices); } clEnv->number_devices=0; } static inline MagickBooleanType MagickCreateDirectory(const char *path) { int status; #ifdef MAGICKCORE_WINDOWS_SUPPORT status=mkdir(path); #else status=mkdir(path,0777); #endif return(status == 0 ? MagickTrue : MagickFalse); } static inline void InitAccelerateTimer(AccelerateTimer *timer) { #ifdef _WIN32 QueryPerformanceFrequency((LARGE_INTEGER*)&timer->freq); #else timer->freq=(long long)1.0E3; #endif timer->clocks=0; timer->start=0; } static inline double ReadAccelerateTimer(AccelerateTimer *timer) { return (double)timer->clocks/(double)timer->freq; } static inline void StartAccelerateTimer(AccelerateTimer* timer) { #ifdef _WIN32 QueryPerformanceCounter((LARGE_INTEGER*)&timer->start); #else struct timeval s; gettimeofday(&s,0); timer->start=(long long)s.tv_sec*(long long)1.0E3+(long long)s.tv_usec/ (long long)1.0E3; #endif } static inline void StopAccelerateTimer(AccelerateTimer *timer) { long long n; n=0; #ifdef _WIN32 QueryPerformanceCounter((LARGE_INTEGER*)&(n)); #else struct timeval s; gettimeofday(&s,0); n=(long long)s.tv_sec*(long long)1.0E3+(long long)s.tv_usec/ (long long)1.0E3; #endif n-=timer->start; timer->start=0; timer->clocks+=n; } static const char *GetOpenCLCacheDirectory() { if (cache_directory == (char *) NULL) { if (cache_directory_lock == (SemaphoreInfo *) NULL) ActivateSemaphoreInfo(&cache_directory_lock); LockSemaphoreInfo(cache_directory_lock); if (cache_directory == (char *) NULL) { char *home, path[MagickPathExtent], *temp; MagickBooleanType status; struct stat attributes; temp=(char *) NULL; home=GetEnvironmentValue("MAGICK_OPENCL_CACHE_DIR"); if (home == (char *) NULL) { home=GetEnvironmentValue("XDG_CACHE_HOME"); #if defined(MAGICKCORE_WINDOWS_SUPPORT) || defined(__MINGW32__) if (home == (char *) NULL) home=GetEnvironmentValue("LOCALAPPDATA"); if (home == (char *) NULL) home=GetEnvironmentValue("APPDATA"); if (home == (char *) NULL) home=GetEnvironmentValue("USERPROFILE"); #endif } if (home != (char *) NULL) { /* first check if $HOME exists */ (void) FormatLocaleString(path,MagickPathExtent,"%s",home); status=GetPathAttributes(path,&attributes); if (status == MagickFalse) status=MagickCreateDirectory(path); /* first check if $HOME/ImageMagick exists */ if (status != MagickFalse) { (void) FormatLocaleString(path,MagickPathExtent, "%s%sImageMagick",home,DirectorySeparator); status=GetPathAttributes(path,&attributes); if (status == MagickFalse) status=MagickCreateDirectory(path); } if (status != MagickFalse) { temp=(char*) AcquireCriticalMemory(strlen(path)+1); CopyMagickString(temp,path,strlen(path)+1); } home=DestroyString(home); } else { home=GetEnvironmentValue("HOME"); if (home != (char *) NULL) { /* first check if $HOME/.cache exists */ (void) FormatLocaleString(path,MagickPathExtent,"%s%s.cache", home,DirectorySeparator); status=GetPathAttributes(path,&attributes); if (status == MagickFalse) status=MagickCreateDirectory(path); /* first check if $HOME/.cache/ImageMagick exists */ if (status != MagickFalse) { (void) FormatLocaleString(path,MagickPathExtent, "%s%s.cache%sImageMagick",home,DirectorySeparator, DirectorySeparator); status=GetPathAttributes(path,&attributes); if (status == MagickFalse) status=MagickCreateDirectory(path); } if (status != MagickFalse) { temp=(char*) AcquireCriticalMemory(strlen(path)+1); CopyMagickString(temp,path,strlen(path)+1); } home=DestroyString(home); } } if (temp == (char *) NULL) { temp=AcquireString("?"); (void) LogMagickEvent(AccelerateEvent,GetMagickModule(), "Cannot use cache directory: \"%s\"",path); } else (void) LogMagickEvent(AccelerateEvent,GetMagickModule(), "Using cache directory: \"%s\"",temp); cache_directory=temp; } UnlockSemaphoreInfo(cache_directory_lock); } if (*cache_directory == '?') return((const char *) NULL); return(cache_directory); } static void SelectOpenCLDevice(MagickCLEnv clEnv,cl_device_type type) { MagickCLDevice device; size_t i, j; (void) LogMagickEvent(AccelerateEvent,GetMagickModule(), "Selecting device for type: %d",(int) type); for (i = 0; i < clEnv->number_devices; i++) clEnv->devices[i]->enabled=MagickFalse; for (i = 0; i < clEnv->number_devices; i++) { device=clEnv->devices[i]; if (device->type != type) continue; device->enabled=MagickTrue; (void) LogMagickEvent(AccelerateEvent,GetMagickModule(), "Selected device: %s",device->name); for (j = i+1; j < clEnv->number_devices; j++) { MagickCLDevice other_device; other_device=clEnv->devices[j]; if (IsSameOpenCLDevice(device,other_device)) other_device->enabled=MagickTrue; } } } static size_t StringSignature(const char* string) { size_t n, i, j, signature, stringLength; union { const char* s; const size_t* u; } p; stringLength=(size_t) strlen(string); signature=stringLength; n=stringLength/sizeof(size_t); p.s=string; for (i = 0; i < n; i++) signature^=p.u[i]; if (n * sizeof(size_t) != stringLength) { char padded[4]; j=n*sizeof(size_t); for (i = 0; i < 4; i++, j++) { if (j < stringLength) padded[i]=p.s[j]; else padded[i]=0; } p.s=padded; signature^=p.u[0]; } return(signature); } static void DestroyMagickCLCacheInfo(MagickCLCacheInfo info) { ssize_t i; for (i=0; i < (ssize_t) info->event_count; i++) openCL_library->clReleaseEvent(info->events[i]); info->events=(cl_event *) RelinquishMagickMemory(info->events); if (info->buffer != (cl_mem) NULL) openCL_library->clReleaseMemObject(info->buffer); RelinquishSemaphoreInfo(&info->events_semaphore); ReleaseOpenCLDevice(info->device); RelinquishMagickMemory(info); } /* Provide call to OpenCL library methods */ MagickPrivate cl_mem CreateOpenCLBuffer(MagickCLDevice device, cl_mem_flags flags,size_t size,void *host_ptr) { return(openCL_library->clCreateBuffer(device->context,flags,size,host_ptr, (cl_int *) NULL)); } MagickPrivate void ReleaseOpenCLKernel(cl_kernel kernel) { (void) openCL_library->clReleaseKernel(kernel); } MagickPrivate void ReleaseOpenCLMemObject(cl_mem memobj) { (void) openCL_library->clReleaseMemObject(memobj); } MagickPrivate void RetainOpenCLMemObject(cl_mem memobj) { (void) openCL_library->clRetainMemObject(memobj); } MagickPrivate cl_int SetOpenCLKernelArg(cl_kernel kernel,size_t arg_index, size_t arg_size,const void *arg_value) { return(openCL_library->clSetKernelArg(kernel,(cl_uint) arg_index,arg_size, arg_value)); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + A c q u i r e M a g i c k C L C a c h e I n f o % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % AcquireMagickCLCacheInfo() acquires an OpenCL cache info structure. % % The format of the AcquireMagickCLCacheInfo method is: % % MagickCLCacheInfo AcquireMagickCLCacheInfo(MagickCLDevice device, % Quantum *pixels,const MagickSizeType length) % % A description of each parameter follows: % % o device: the OpenCL device. % % o pixels: the pixel buffer of the image. % % o length: the length of the pixel buffer. % */ MagickPrivate MagickCLCacheInfo AcquireMagickCLCacheInfo(MagickCLDevice device, Quantum *pixels,const MagickSizeType length) { cl_int status; MagickCLCacheInfo info; info=(MagickCLCacheInfo) AcquireCriticalMemory(sizeof(*info)); (void) memset(info,0,sizeof(*info)); LockSemaphoreInfo(openCL_lock); device->requested++; UnlockSemaphoreInfo(openCL_lock); info->device=device; info->length=length; info->pixels=pixels; info->events_semaphore=AcquireSemaphoreInfo(); info->buffer=openCL_library->clCreateBuffer(device->context, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,(size_t) length,(void *) pixels, &status); if (status == CL_SUCCESS) return(info); DestroyMagickCLCacheInfo(info); return((MagickCLCacheInfo) NULL); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % A c q u i r e M a g i c k C L D e v i c e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % AcquireMagickCLDevice() acquires an OpenCL device % % The format of the AcquireMagickCLDevice method is: % % MagickCLDevice AcquireMagickCLDevice() % */ static MagickCLDevice AcquireMagickCLDevice() { MagickCLDevice device; device=(MagickCLDevice) AcquireMagickMemory(sizeof(*device)); if (device != NULL) { (void) memset(device,0,sizeof(*device)); ActivateSemaphoreInfo(&device->lock); device->score=MAGICKCORE_OPENCL_UNDEFINED_SCORE; device->command_queues_index=-1; device->enabled=MagickTrue; } return(device); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % A c q u i r e M a g i c k C L E n v % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % AcquireMagickCLEnv() allocates the MagickCLEnv structure % */ static MagickCLEnv AcquireMagickCLEnv(void) { const char *option; MagickCLEnv clEnv; clEnv=(MagickCLEnv) AcquireMagickMemory(sizeof(*clEnv)); if (clEnv != (MagickCLEnv) NULL) { (void) memset(clEnv,0,sizeof(*clEnv)); ActivateSemaphoreInfo(&clEnv->lock); clEnv->cpu_score=MAGICKCORE_OPENCL_UNDEFINED_SCORE; clEnv->enabled=MagickFalse; option=getenv("MAGICK_OCL_DEVICE"); if (option != (const char *) NULL) { if ((IsStringTrue(option) != MagickFalse) || (strcmp(option,"GPU") == 0) || (strcmp(option,"CPU") == 0)) clEnv->enabled=MagickTrue; } } return clEnv; } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + A c q u i r e O p e n C L C o m m a n d Q u e u e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % AcquireOpenCLCommandQueue() acquires an OpenCL command queue % % The format of the AcquireOpenCLCommandQueue method is: % % cl_command_queue AcquireOpenCLCommandQueue(MagickCLDevice device) % % A description of each parameter follows: % % o device: the OpenCL device. % */ MagickPrivate cl_command_queue AcquireOpenCLCommandQueue(MagickCLDevice device) { cl_command_queue queue; cl_command_queue_properties properties; assert(device != (MagickCLDevice) NULL); LockSemaphoreInfo(device->lock); if ((device->profile_kernels == MagickFalse) && (device->command_queues_index >= 0)) { queue=device->command_queues[device->command_queues_index--]; UnlockSemaphoreInfo(device->lock); } else { UnlockSemaphoreInfo(device->lock); properties=0; if (device->profile_kernels != MagickFalse) properties=CL_QUEUE_PROFILING_ENABLE; queue=openCL_library->clCreateCommandQueue(device->context, device->deviceID,properties,(cl_int *) NULL); } return(queue); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % + A c q u i r e O p e n C L K e r n e l % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % AcquireOpenCLKernel() acquires an OpenCL kernel % % The format of the AcquireOpenCLKernel method is: % % cl_kernel AcquireOpenCLKernel(MagickCLEnv clEnv, % MagickOpenCLProgram program, const char* kernelName) % % A description of each parameter follows: % % o clEnv: the OpenCL environment. % % o program: the OpenCL program module that the kernel belongs to. % % o kernelName: the name of the kernel % */ MagickPrivate cl_kernel AcquireOpenCLKernel(MagickCLDevice device, const char *kernel_name) { cl_kernel kernel; assert(device != (MagickCLDevice) NULL); (void) LogMagickEvent(AccelerateEvent,GetMagickModule(),"Using kernel: %s", kernel_name); kernel=openCL_library->clCreateKernel(device->program,kernel_name, (cl_int *) NULL); return(kernel); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % A u t o S e l e c t O p e n C L D e v i c e s % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % AutoSelectOpenCLDevices() determines the best device based on the % information from the micro-benchmark. % % The format of the AutoSelectOpenCLDevices method is: % % void AcquireOpenCLKernel(MagickCLEnv clEnv,ExceptionInfo *exception) % % A description of each parameter follows: % % o clEnv: the OpenCL environment. % % o exception: return any errors or warnings in this structure. % */ static void LoadOpenCLDeviceBenchmark(MagickCLEnv clEnv,const char *xml) { char keyword[MagickPathExtent], *token; const char *q; MagickCLDeviceBenchmark *device_benchmark; size_t i, extent; if (xml == (char *) NULL) return; device_benchmark=(MagickCLDeviceBenchmark *) NULL; token=AcquireString(xml); extent=strlen(token)+MagickPathExtent; for (q=(char *) xml; *q != '\0'; ) { /* Interpret XML. */ (void) GetNextToken(q,&q,extent,token); if (*token == '\0') break; (void) CopyMagickString(keyword,token,MagickPathExtent); if (LocaleNCompare(keyword,"",2) != 0) && (*q != '\0')) (void) GetNextToken(q,&q,extent,token); continue; } if (LocaleNCompare(keyword,"