001/* 002 * JavaCL - Java API and utilities for OpenCL 003 * http://javacl.googlecode.com/ 004 * 005 * Copyright (c) 2009-2013, Olivier Chafik (http://ochafik.com/) 006 * All rights reserved. 007 * 008 * Redistribution and use in source and binary forms, with or without 009 * modification, are permitted provided that the following conditions are met: 010 * 011 * * Redistributions of source code must retain the above copyright 012 * notice, this list of conditions and the following disclaimer. 013 * * Redistributions in binary form must reproduce the above copyright 014 * notice, this list of conditions and the following disclaimer in the 015 * documentation and/or other materials provided with the distribution. 016 * * Neither the name of Olivier Chafik nor the 017 * names of its contributors may be used to endorse or promote products 018 * derived from this software without specific prior written permission. 019 * 020 * THIS SOFTWARE IS PROVIDED BY OLIVIER CHAFIK AND CONTRIBUTORS ``AS IS'' AND ANY 021 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 022 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 023 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 024 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 025 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 026 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 027 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 028 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 029 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 030 */ 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050package com.nativelibs4java.opencl; 051 052import static com.nativelibs4java.opencl.CLException.error; 053import com.nativelibs4java.opencl.CLPlatform.DeviceFeature; 054 055import java.io.File; 056import java.io.IOException; 057 058import java.util.ArrayList; 059import java.util.Arrays; 060import java.util.List; 061import java.util.Map; 062import java.util.logging.*; 063 064import java.lang.reflect.InvocationHandler; 065import java.lang.reflect.Method; 066import java.lang.reflect.Proxy; 067 068import com.nativelibs4java.opencl.library.OpenCLLibrary; 069import com.nativelibs4java.opencl.library.IOpenCLLibrary; 070import com.nativelibs4java.opencl.library.IOpenCLLibrary.cl_platform_id; 071import org.bridj.*; 072import org.bridj.ann.Ptr; 073 074import org.bridj.util.ProcessUtils; 075import org.bridj.util.StringUtils; 076import static org.bridj.Pointer.*; 077 078/** 079 * Entry point class for the OpenCL4Java Object-oriented wrappers around the OpenCL API.<br> 080 * @author Olivier Chafik 081 */ 082public class JavaCL { 083 084 static final boolean debug = "true".equals(System.getProperty("javacl.debug")) || "1".equals(System.getenv("JAVACL_DEBUG")); 085 static final boolean verbose = debug || "true".equals(System.getProperty("javacl.verbose")) || "1".equals(System.getenv("JAVACL_VERBOSE")); 086 static final boolean logCalls = "true".equals(System.getProperty("javacl.logCalls")) || "1".equals(System.getenv("JAVACL_LOG_CALLS")); 087 static final int minLogLevel = Level.WARNING.intValue(); 088 089 static final String JAVACL_DEBUG_COMPILER_FLAGS_PROP = "JAVACL_DEBUG_COMPILER_FLAGS"; 090 static List<String> DEBUG_COMPILER_FLAGS; 091 092 static boolean shouldLog(Level level) { 093 return verbose || level.intValue() >= minLogLevel; 094 } 095 static boolean log(Level level, String message, Throwable ex) { 096 if (!shouldLog(level)) 097 return true; 098 Logger.getLogger(JavaCL.class.getSimpleName()).log(level, message, ex); 099 return true; 100 } 101 102 static boolean log(Level level, String message) { 103 log(level, message, null); 104 return true; 105 } 106 107 static void check(boolean test, String message, Object... formatArgs) { 108 if (!test) throw new RuntimeException(String.format(message, formatArgs)); 109 } 110 111 private static int getPlatformIDs(int count, Pointer<cl_platform_id> out, Pointer<Integer> pCount) { 112 assert (count == 0) ^ (pCount == null); 113 assert (count == 0) == (out == null); 114 if (hasIcd == null || hasIcd.booleanValue()) { 115 try { 116 int ret = CL.clIcdGetPlatformIDsKHR(count, getPeer(out), getPeer(pCount)); 117 if (hasIcd == null) 118 hasIcd = true; 119 return ret; 120 } catch (Throwable th) { 121 hasIcd = false; 122 return CL.clGetPlatformIDs(count, getPeer(out), getPeer(pCount)); 123 } 124 } else { 125 return CL.clGetPlatformIDs(count, getPeer(out), getPeer(pCount)); 126 } 127 } 128 129 @org.bridj.ann.Library("OpenCLProbe") 130 @org.bridj.ann.Convention(org.bridj.ann.Convention.Style.StdCall) 131 public static class OpenCLProbeLibrary { 132 @org.bridj.ann.Optional 133 public native static synchronized int clGetPlatformIDs(int cl_uint1, Pointer<cl_platform_id > cl_platform_idPtr1, Pointer<Integer > cl_uintPtr1); 134 @org.bridj.ann.Optional 135 public native static synchronized int clIcdGetPlatformIDsKHR(int cl_uint1, Pointer<cl_platform_id > cl_platform_idPtr1, Pointer<Integer > cl_uintPtr1); 136 @org.bridj.ann.Optional 137 public native static int clGetPlatformInfo(@Ptr long cl_platform_id1, int cl_platform_info1, @Ptr long size_t1, @Ptr long voidPtr1, @Ptr long size_tPtr1); 138 139 protected static CLInfoGetter infos = new CLInfoGetter() { 140 @Override 141 protected int getInfo(long entity, int infoTypeEnum, long size, Pointer out, Pointer<SizeT> sizeOut) { 142 return clGetPlatformInfo(entity, infoTypeEnum, size, getPeer(out), getPeer(sizeOut)); 143 } 144 }; 145 146 private static int getPlatformIDs(int count, Pointer<cl_platform_id> out, Pointer<Integer> pCount) { 147 try { 148 return clIcdGetPlatformIDsKHR(count, out, pCount); 149 } catch (Throwable th) { 150 return clGetPlatformIDs(count, out, pCount); 151 } 152 } 153 private static Pointer<cl_platform_id> getPlatformIDs() { 154 Pointer<Integer> pCount = allocateInt(); 155 error(getPlatformIDs(0, null, pCount)); 156 157 int nPlats = pCount.getInt(); 158 if (nPlats == 0) 159 return null; 160 161 Pointer<cl_platform_id> ids = allocateTypedPointers(cl_platform_id.class, nPlats); 162 error(getPlatformIDs(nPlats, ids, null)); 163 return ids; 164 } 165 public static boolean hasOpenCL1_0() { 166 Pointer<cl_platform_id> ids = getPlatformIDs(); 167 if (ids == null) 168 return false; 169 170 for (cl_platform_id id : ids) 171 if (isOpenCL1_0(id)) 172 return true; 173 return false; 174 } 175 public static boolean isOpenCL1_0(cl_platform_id platform) { 176 String version = infos.getString(getPeer(platform), OpenCLLibrary.CL_PLATFORM_VERSION); 177 return version.matches("OpenCL 1\\.0.*"); 178 } 179 public static boolean isValid() { 180 try { 181 Pointer<cl_platform_id> ids = getPlatformIDs(); 182 return ids != null; 183 } catch (Throwable th) { 184 return false; 185 } 186 } 187 } 188 189 static final IOpenCLLibrary CL; 190 static Boolean hasIcd; 191 static { 192 if (Platform.isLinux()) { 193 String amdAppBase = "/opt/AMDAPP/lib"; 194 BridJ.addLibraryPath(amdAppBase + "/" + (Platform.is64Bits() ? "x86_64" : "x86")); 195 BridJ.addLibraryPath(amdAppBase); 196 } 197 boolean needsAdditionalSynchronization = false; 198 { 199 OpenCLProbeLibrary probe = null; 200 try { 201 try { 202 BridJ.setNativeLibraryActualName("OpenCLProbe", "OpenCL"); 203 BridJ.register(); 204 } catch (Throwable th) {} 205 206 probe = new OpenCLProbeLibrary(); 207 208 if (!probe.isValid()) { 209 BridJ.unregister(OpenCLProbeLibrary.class); 210 //BridJ.setNativeLibraryActualName("OpenCLProbe", "OpenCL"); 211 String alt; 212 if (Platform.is64Bits() && (BridJ.getNativeLibraryFile(alt = "atiocl64") != null || BridJ.getNativeLibraryFile(alt = "amdocl64") != null) || 213 BridJ.getNativeLibraryFile(alt = "atiocl32") != null || 214 BridJ.getNativeLibraryFile(alt = "atiocl") != null || 215 BridJ.getNativeLibraryFile(alt = "amdocl32") != null || 216 BridJ.getNativeLibraryFile(alt = "amdocl") != null) 217 { 218 log(Level.INFO, "Hacking around ATI's weird driver bugs (using atiocl/amdocl library instead of OpenCL)", null); 219 BridJ.setNativeLibraryActualName("OpenCL", alt); 220 } 221 BridJ.register(OpenCLProbeLibrary.class); 222 } 223 224 225 if (probe.hasOpenCL1_0()) { 226 needsAdditionalSynchronization = true; 227 log(Level.WARNING, "At least one OpenCL platform uses OpenCL 1.0, which is not thread-safe: will use (slower) synchronized low-level bindings."); 228 } 229 } finally { 230 if (probe != null) 231 BridJ.unregister(OpenCLProbeLibrary.class); 232 probe = null; 233 } 234 } 235 236 if (debug) { 237 String debugArgs = System.getenv(JAVACL_DEBUG_COMPILER_FLAGS_PROP); 238 if (debugArgs != null) 239 DEBUG_COMPILER_FLAGS = Arrays.asList(debugArgs.split(" ")); 240 else if (Platform.isMacOSX()) 241 DEBUG_COMPILER_FLAGS = Arrays.asList("-g"); 242 else 243 DEBUG_COMPILER_FLAGS = Arrays.asList("-O0", "-g"); 244 245 int pid = ProcessUtils.getCurrentProcessId(); 246 log(Level.INFO, "Debug mode enabled with compiler flags \"" + StringUtils.implode(DEBUG_COMPILER_FLAGS, " ") + "\" (can be overridden with env. var. JAVACL_DEBUG_COMPILER_FLAGS_PROP)"); 247 log(Level.INFO, "You can debug your kernels with GDB using one of the following commands :\n" 248 + "\tsudo gdb --tui --pid=" + pid + "\n" 249 + "\tsudo ddd --debugger \"gdb --pid=" + pid + "\"\n" 250 + "More info here :\n" 251 + "\thttp://code.google.com/p/javacl/wiki/DebuggingKernels"); 252 253 254 } 255 Class<? extends OpenCLLibrary> libraryClass = OpenCLLibrary.class; 256 if (needsAdditionalSynchronization) { 257 try { 258 libraryClass = BridJ.subclassWithSynchronizedNativeMethods(libraryClass); 259 } catch (Throwable ex) { 260 throw new RuntimeException("Failed to create a synchronized version of the OpenCL API bindings: " + ex, ex); 261 } 262 } 263 BridJ.register(libraryClass); 264 try { 265 IOpenCLLibrary cl = libraryClass.newInstance(); 266 CL = logCalls ? wrapWithLogs(cl) : cl; 267 } catch (Throwable ex) { 268 throw new RuntimeException("Failed to instantiate library " + libraryClass.getName() + ": " + ex, ex); 269 } 270 } 271 272 private static IOpenCLLibrary wrapWithLogs(final IOpenCLLibrary cl) { 273 return (IOpenCLLibrary) Proxy.newProxyInstance(JavaCL.class.getClassLoader(), new Class[] { IOpenCLLibrary.class }, new InvocationHandler() { 274 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 275 log(Level.INFO, method.getName() + "(" + StringUtils.implode(args, ", ") + ")"); 276 Object ret = method.invoke(cl, args); 277 log(Level.INFO, "\t" + method.getName() + " -> " + ret); 278 return ret; 279 } 280 }); 281 } 282 283 /** 284 * List the OpenCL implementations that contain at least one GPU device. 285 */ 286 public static CLPlatform[] listGPUPoweredPlatforms() { 287 CLPlatform[] platforms = listPlatforms(); 288 List<CLPlatform> out = new ArrayList<CLPlatform>(platforms.length); 289 for (CLPlatform platform : platforms) { 290 if (platform.listGPUDevices(true).length > 0) 291 out.add(platform); 292 } 293 return out.toArray(new CLPlatform[out.size()]); 294 } 295 /** 296 * Lists all available OpenCL implementations. 297 */ 298 public static CLPlatform[] listPlatforms() { 299 Pointer<Integer> pCount = allocateInt(); 300 error(getPlatformIDs(0, null, pCount)); 301 302 int nPlats = pCount.getInt(); 303 if (nPlats == 0) 304 return new CLPlatform[0]; 305 306 Pointer<cl_platform_id> ids = allocateTypedPointers(cl_platform_id.class, nPlats); 307 308 error(getPlatformIDs(nPlats, ids, null)); 309 CLPlatform[] platforms = new CLPlatform[nPlats]; 310 311 for (int i = 0; i < nPlats; i++) { 312 platforms[i] = new CLPlatform(ids.getSizeTAtIndex(i)); 313 } 314 return platforms; 315 } 316 317 /** 318 * Creates an OpenCL context formed of the provided devices.<br> 319 * It is generally not a good idea to create a context with more than one device, 320 * because much data is shared between all the devices in the same context. 321 * @param devices devices that are to form the new context 322 * @return new OpenCL context 323 */ 324 public static CLContext createContext(Map<CLPlatform.ContextProperties, Object> contextProperties, CLDevice... devices) { 325 return devices[0].getPlatform().createContext(contextProperties, devices); 326 } 327 328 /** 329 * Allows the implementation to release the resources allocated by the OpenCL compiler. <br> 330 * This is a hint from the application and does not guarantee that the compiler will not be used in the future or that the compiler will actually be unloaded by the implementation. <br> 331 * Calls to Program.build() after unloadCompiler() will reload the compiler, if necessary, to build the appropriate program executable. 332 */ 333 public static void unloadCompiler() { 334 error(CL.clUnloadCompiler()); 335 } 336 337 /** 338 * Returns the "best" OpenCL device (currently, the one that has the largest amount of compute units).<br> 339 * For more control on what is to be considered a better device, please use the {@link JavaCL#getBestDevice(CLPlatform.DeviceFeature[]) } variant.<br> 340 * This is currently equivalent to <code>getBestDevice(MaxComputeUnits)</code> 341 */ 342 public static CLDevice getBestDevice() { 343 return getBestDevice(CLPlatform.DeviceFeature.MaxComputeUnits); 344 } 345 /** 346 * Returns the "best" OpenCL device based on the comparison of the provided prioritized device feature.<br> 347 * The returned device does not necessarily exhibit the features listed in preferredFeatures, but it has the best ordered composition of them.<br> 348 * For instance on a system with a GPU and a CPU device, <code>JavaCL.getBestDevice(CPU, MaxComputeUnits)</code> will return the CPU device, but on another system with two GPUs and no CPU device it will return the GPU that has the most compute units. 349 */ 350 public static CLDevice getBestDevice(CLPlatform.DeviceFeature... preferredFeatures) { 351 List<CLDevice> devices = new ArrayList<CLDevice>(); 352 for (CLPlatform platform : listPlatforms()) 353 devices.addAll(Arrays.asList(platform.listAllDevices(true))); 354 return CLPlatform.getBestDevice(Arrays.asList(preferredFeatures), devices); 355 } 356 /** 357 * Creates an OpenCL context with the "best" device (see {@link JavaCL#getBestDevice() }) 358 */ 359 public static CLContext createBestContext() { 360 return createBestContext(DeviceFeature.MaxComputeUnits); 361 } 362 363 /** 364 * Creates an OpenCL context with the "best" device based on the comparison of the provided 365 * prioritized device feature (see {@link JavaCL#getBestDevice(CLPlatform.DeviceFeature...) }) 366 */ 367 public static CLContext createBestContext(CLPlatform.DeviceFeature... preferredFeatures) { 368 CLDevice device = getBestDevice(preferredFeatures); 369 return device.getPlatform().createContext(null, device); 370 } 371 372 /** 373 * Creates an OpenCL context able to share entities with the current OpenGL context. 374 * @throws RuntimeException if JavaCL is unable to create an OpenGL-shared OpenCL context. 375 */ 376 public static CLContext createContextFromCurrentGL() { 377 RuntimeException first = null; 378 for (CLPlatform platform : listPlatforms()) { 379 try { 380 CLContext ctx = platform.createContextFromCurrentGL(); 381 if (ctx != null) 382 return ctx; 383 } catch (RuntimeException ex) { 384 if (first == null) 385 first = ex; 386 387 } 388 } 389 throw new RuntimeException("Failed to create an OpenCL context based on the current OpenGL context", first); 390 } 391 392 static File userJavaCLDir = new File(new File(System.getProperty("user.home")), ".javacl"); 393 static File userCacheDir = new File(userJavaCLDir, "cache"); 394 395 static synchronized File createTempFile(String prefix, String suffix, String category) { 396 File dir = new File(userJavaCLDir, category); 397 dir.mkdirs(); 398 try { 399 return File.createTempFile(prefix, suffix, dir); 400 } catch (IOException ex) { 401 throw new RuntimeException("Failed to create a temporary directory for category '" + category + "' in " + userJavaCLDir + ": " + ex.getMessage(), ex); 402 } 403 } 404 static synchronized File createTempDirectory(String prefix, String suffix, String category) { 405 File file = createTempFile(prefix, suffix, category); 406 file.delete(); 407 file.mkdir(); 408 return file; 409 } 410}