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}