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 com.nativelibs4java.opencl.library.OpenGLContextUtils;
053import com.nativelibs4java.util.EnumValue;
054import com.nativelibs4java.util.EnumValues;
055import static com.nativelibs4java.opencl.library.OpenCLLibrary.*;
056import static com.nativelibs4java.opencl.library.IOpenCLLibrary.*;
057
058import org.bridj.*;
059import org.bridj.ann.*;
060import static org.bridj.Pointer.*;
061
062import java.nio.ByteOrder;
063import java.util.*;
064import java.util.regex.Matcher;
065import java.util.regex.Pattern;
066import java.util.logging.*;
067import java.util.concurrent.ConcurrentHashMap;
068
069import static com.nativelibs4java.opencl.JavaCL.*;
070import static com.nativelibs4java.opencl.CLException.*;
071
072/**
073 * OpenCL implementation entry point.
074 * see {@link JavaCL#listPlatforms() } 
075 * @author Olivier Chafik
076 */
077public class CLPlatform extends CLAbstractEntity {
078
079    CLPlatform(long platform) {
080        super(platform, true);
081    }
082    
083        protected static CLInfoGetter infos = new CLInfoGetter() {
084                @Override
085                protected int getInfo(long entity, int infoTypeEnum, long size, Pointer out, Pointer<SizeT> sizeOut) {
086                        return CL.clGetPlatformInfo(entity, infoTypeEnum, size, getPeer(out), getPeer(sizeOut));
087                }
088        };
089
090    @Override
091    public String toString() {
092        return toString(new StringBuilder()).toString();
093    }
094    StringBuilder toString(StringBuilder out) {
095        out.
096                        append(getName()). 
097                        append(" {vendor: ").append(getVendor()).
098                        append(", version: ").append(getVersion()).
099                        append(", profile: ").append(getProfile()).
100                        append(", extensions: ").append(Arrays.toString(getExtensions())).
101                        append("}");
102                return out;
103    }
104
105    @Override
106    protected void clear() {
107    }
108
109    /**
110     * Lists all the devices of the platform
111     * @param onlyAvailable if true, only returns devices that are available
112     * see {@link CLPlatform#listDevices(CLDevice.Type, boolean) }
113     */
114    public CLDevice[] listAllDevices(boolean onlyAvailable) {
115        return listDevices(CLDevice.Type.All, onlyAvailable);
116    }
117
118    /**
119     * Lists all the GPU devices of the platform
120     * @param onlyAvailable if true, only returns GPU devices that are available
121     * see {@link CLPlatform#listDevices(CLDevice.Type, boolean) }
122     */
123    public CLDevice[] listGPUDevices(boolean onlyAvailable) {
124        try {
125            return listDevices(CLDevice.Type.GPU, onlyAvailable);
126        } catch (CLException ex) {
127            if (ex.getCode() == CL_DEVICE_NOT_FOUND) {
128                return new CLDevice[0];
129            }
130            throw new RuntimeException("Unexpected OpenCL error", ex);
131        }
132    }
133
134    /**
135     * Lists all the CPU devices of the platform
136     * @param onlyAvailable if true, only returns CPU devices that are available
137     * see {@link CLPlatform#listDevices(CLDevice.Type, boolean) }
138     */
139    public CLDevice[] listCPUDevices(boolean onlyAvailable) {
140        try {
141            return listDevices(CLDevice.Type.CPU, onlyAvailable);
142        } catch (CLException ex) {
143            if (ex.getCode() == CL_DEVICE_NOT_FOUND) {
144                return new CLDevice[0];
145            }
146            throw new RuntimeException("Unexpected OpenCL error", ex);
147        }
148    }
149
150    private CLDevice[] getDevices(Pointer<SizeT> ids, boolean onlyAvailable) {
151        int nDevs = (int)ids.getValidElements();
152        CLDevice[] devices;
153        if (onlyAvailable) {
154            List<CLDevice> list = new ArrayList<CLDevice>(nDevs);
155            for (int i = 0; i < nDevs; i++) {
156                CLDevice device = new CLDevice(this, ids.getSizeTAtIndex(i));
157                if (device.isAvailable()) {
158                    list.add(device);
159                }
160            }
161            devices = list.toArray(new CLDevice[list.size()]);
162        } else {
163            devices = new CLDevice[nDevs];
164            for (int i = 0; i < nDevs; i++) {
165                devices[i] = new CLDevice(this, ids.getSizeTAtIndex(i));
166            }
167        }
168        return devices;
169    }
170
171    long[] getContextProps(Map<ContextProperties, Object> contextProperties) {
172        int nContextProperties = contextProperties == null ? 0 : contextProperties.size();
173        final long[] properties = new long[(nContextProperties + 1) * 2 + 1];
174        properties[0] = CL_CONTEXT_PLATFORM;
175        properties[1] = getEntity();
176        int iProp = 2;
177        if (nContextProperties != 0) {
178                        for (Map.Entry<ContextProperties, Object> e : contextProperties.entrySet()) {
179                                //if (!(v instanceof Number)) throw new IllegalArgumentException("Invalid context property value for '" + e.getKey() + ": " + v);
180                                properties[iProp++] = e.getKey().value();
181                                Object v = e.getValue();
182                                if (v instanceof Number)
183                                        properties[iProp++] = ((Number)v).longValue();
184                                else if (v instanceof Pointer)
185                                        properties[iProp++] = ((Pointer)v).getPeer();
186                                else
187                                        throw new IllegalArgumentException("Cannot convert value " + v + " to a context property value !");
188                        }
189                }
190        //properties[iProp] = 0;
191        return properties;
192    }
193
194    /**
195     * Enums used to indicate how to choose the best CLDevice.
196     */
197    public enum DeviceFeature {
198        /**
199         * Prefer CPU devices (see {@link CLDevice#getType() })
200         */
201        CPU {
202            Comparable extractValue(CLDevice device) {
203                return device.getType().contains(CLDevice.Type.CPU) ? 1 : 0;
204            }
205        },
206        /**
207         * Prefer GPU devices (see {@link CLDevice#getType() })
208         */
209        GPU {
210            Comparable extractValue(CLDevice device) {
211                return device.getType().contains(CLDevice.Type.GPU) ? 1 : 0;
212            }
213        },
214        /**
215         * Prefer Accelerator devices (see {@link CLDevice#getType() })
216         */
217        Accelerator {
218            Comparable extractValue(CLDevice device) {
219                return device.getType().contains(CLDevice.Type.Accelerator) ? 1 : 0;
220            }
221        },
222        /**
223         * Prefer devices with the most compute units (see {@link CLDevice#getMaxComputeUnits() })
224         */
225        MaxComputeUnits {
226            Comparable extractValue(CLDevice device) {
227                return device.getMaxComputeUnits();
228            }
229        },
230        /**
231         * Prefer devices with the same byte ordering as the hosting platform (see {@link CLDevice#getByteOrder() })
232         */
233        NativeEndianness {
234            Comparable extractValue(CLDevice device) {
235                return device.getKernelsDefaultByteOrder() == ByteOrder.nativeOrder() ? 1 : 0;
236            }
237        },
238        /**
239         * Prefer devices that support double-precision float computations (see {@link CLDevice#isDoubleSupported() })
240         */
241        DoubleSupport {
242            Comparable extractValue(CLDevice device) {
243                return device.isDoubleSupported() ? 1 : 0;
244            }
245        },
246        /**
247         * Prefer devices that support images and with the most supported image formats (see {@link CLDevice#hasImageSupport() })
248         */
249        ImageSupport {
250            Comparable extractValue(CLDevice device) {
251                return device.hasImageSupport() ? 1 : 0;
252            }
253        },
254        /**
255         * Prefer devices that support out of order queues (see {@link CLDevice#hasOutOfOrderQueueSupport() })
256         */
257        OutOfOrderQueueSupport {
258            Comparable extractValue(CLDevice device) {
259                return device.hasOutOfOrderQueueSupport() ? 1 : 0;
260            }
261        },
262        /**
263         * Prefer devices with the greatest variety of supported image formats (see {@link CLContext#getSupportedImageFormats(CLMem.Flags, CLMem.ObjectType) })
264         */
265        MostImageFormats {
266            Comparable extractValue(CLDevice device) {
267                if (!device.hasImageSupport())
268                    return 0;
269                // TODO: fix that ugly hack ?
270                CLContext context = JavaCL.createContext(null, device);
271                try {
272                    return (Integer)context.getSupportedImageFormats(CLMem.Flags.ReadWrite, CLMem.ObjectType.Image2D).length;
273                } finally {
274                    context.release();
275                }
276            }
277        };
278
279        Comparable extractValue(CLDevice device) {
280            throw new RuntimeException();
281        }
282    }
283
284    public static class DeviceComparator implements Comparator<CLDevice> {
285
286        private final List<DeviceFeature> evals;
287        public DeviceComparator(List<DeviceFeature> evals) {
288            this.evals = evals;
289        }
290
291        @Override
292        public int compare(CLDevice a, CLDevice b) {
293            for (DeviceFeature eval : evals) {
294                if (eval == null)
295                    continue;
296                
297                Comparable va = eval.extractValue(a), vb = eval.extractValue(b);
298                int c = va.compareTo(vb);
299                if (c != 0)
300                    return c;
301            }
302            return 0;
303        }
304        
305    }
306        public static CLDevice getBestDevice(List<DeviceFeature> evals, Collection<CLDevice> devices) {
307        List<CLDevice> list = new ArrayList<CLDevice>(devices);
308        Collections.sort(list, new DeviceComparator(evals));
309        return !list.isEmpty() ? list.get(list.size() - 1) : null;
310    }
311
312    public CLDevice getBestDevice() {
313        return getBestDevice(Arrays.asList(DeviceFeature.MaxComputeUnits), Arrays.asList(listAllDevices(true)));
314    }
315
316    /** Bit values for CL_CONTEXT_PROPERTIES */
317    public enum ContextProperties implements com.nativelibs4java.util.ValuedEnum {
318        //D3D10Device(CL_CONTEXT_D3D10_DEVICE_KHR), 
319        GLContext(CL_GL_CONTEXT_KHR),
320                EGLDisplay(CL_EGL_DISPLAY_KHR),
321                GLXDisplay(CL_GLX_DISPLAY_KHR),
322                WGLHDC(CL_WGL_HDC_KHR),
323        Platform(CL_CONTEXT_PLATFORM),
324        CGLShareGroupApple(CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE),
325                CGLShareGroup(CL_CGL_SHAREGROUP_KHR);
326
327                ContextProperties(long value) { this.value = value; }
328                long value;
329                @Override
330                public long value() { return value; }
331                
332        public static long getValue(EnumSet<ContextProperties> set) {
333            return EnumValues.getValue(set);
334        }
335
336        public static EnumSet<ContextProperties> getEnumSet(long v) {
337            return EnumValues.getEnumSet(v, ContextProperties.class);
338        }
339    }
340
341    public CLContext createContextFromCurrentGL() {
342        return createGLCompatibleContext(listAllDevices(true));
343    }
344
345    static Map<ContextProperties, Object> getGLContextProperties(CLPlatform platform) {
346        Map<ContextProperties, Object> out = new LinkedHashMap<ContextProperties, Object>();
347
348        if (Platform.isMacOSX()) {
349            Pointer<?> context = OpenGLContextUtils.CGLGetCurrentContext();
350            Pointer<?> shareGroup = OpenGLContextUtils.CGLGetShareGroup(context);
351            out.put(ContextProperties.CGLShareGroupApple, shareGroup.getPeer());
352        } else if (Platform.isWindows()) {
353            Pointer<?> context = OpenGLContextUtils.wglGetCurrentContext();
354            Pointer<?> dc = OpenGLContextUtils.wglGetCurrentDC();
355            out.put(ContextProperties.GLContext, context.getPeer());
356            out.put(ContextProperties.WGLHDC, dc.getPeer());
357            out.put(ContextProperties.Platform, platform.getEntity());
358        } else if (Platform.isUnix()) {
359            Pointer<?> context = OpenGLContextUtils.glXGetCurrentContext();
360            Pointer<?> dc = OpenGLContextUtils.glXGetCurrentDisplay();
361            out.put(ContextProperties.GLContext, context.getPeer());
362            out.put(ContextProperties.GLXDisplay, dc.getPeer());
363            out.put(ContextProperties.Platform, platform.getEntity());
364        } else
365            throw new UnsupportedOperationException("Current GL context retrieval not implemented on this platform !");
366        
367        //out.put(ContextProperties.Platform, platform.getEntity().getPointer());
368        
369        return out;
370    }
371    /**
372  * Calls <a href="http://www.khronos.org/registry/cl/sdk/1.2/docs/man/xhtml/clCreateContext.html">clCreateContext</a>.<br>
373         */
374    @Deprecated
375    public CLContext createGLCompatibleContext(CLDevice... devices) {
376        for (CLDevice device : devices) {
377            if (!device.isGLSharingSupported())
378                continue;
379            
380            try {
381                return createContext(getGLContextProperties(this), device);
382            } catch (Throwable th) {}
383        }
384        throw new UnsupportedOperationException("Failed to create an OpenGL-sharing-enabled OpenCL context out of devices " + Arrays.asList(devices));
385    }
386
387    /**
388  * Calls <a href="http://www.khronos.org/registry/cl/sdk/1.2/docs/man/xhtml/clCreateContext.html">clCreateContext</a>.<br>
389     * Creates an OpenCL context formed of the provided devices.<br>
390     * It is generally not a good idea to create a context with more than one device,
391     * because much data is shared between all the devices in the same context.
392     * @param devices devices that are to form the new context
393     * @return new OpenCL context
394     */
395    public CLContext createContext(Map<ContextProperties, Object> contextProperties, CLDevice... devices) {
396        int nDevs = devices.length;
397        if (nDevs == 0) {
398            throw new IllegalArgumentException("Cannot create a context with no associated device !");
399        }
400        Pointer<SizeT> ids = allocateSizeTs(nDevs);
401        for (int i = 0; i < nDevs; i++) {
402            ids.setSizeTAtIndex(i, devices[i].getEntity());
403        }
404
405                        ReusablePointers ptrs = ReusablePointers.get();
406                Pointer<Integer> pErr = ptrs.pErr;
407
408        long[] props = getContextProps(contextProperties);
409        Pointer<SizeT> propsRef = props == null ? null : pointerToSizeTs(props);
410        //System.out.println("ERROR CALLBACK " + Long.toHexString(errCb.getPeer()));
411        long context = CL.clCreateContext(getPeer(propsRef), nDevs, getPeer(ids), 0, 0, getPeer(pErr));
412                error(pErr.getInt());
413;
414        return new CLContext(this, ids, context);
415    }
416
417    /**
418  * Calls <a href="http://www.khronos.org/registry/cl/sdk/1.2/docs/man/xhtml/clGetDeviceIDs.html">clGetDeviceIDs</a>.<br>
419     * List all the devices of the specified types, with only the ones declared as available if onlyAvailable is true.
420     */
421    @SuppressWarnings("deprecation")
422    public CLDevice[] listDevices(CLDevice.Type type, boolean onlyAvailable) {
423        Pointer<Integer> pCount = allocateInt();
424                error(CL.clGetDeviceIDs(getEntity(), type.value(), 0, 0, getPeer(pCount)));
425
426        int nDevs = pCount.getInt();
427        if (nDevs <= 0) {
428            return new CLDevice[0];
429        }
430
431        Pointer<SizeT> ids = allocateSizeTs(nDevs);
432
433        error(CL.clGetDeviceIDs(getEntity(), type.value(), nDevs, getPeer(ids), 0));
434        return getDevices(ids, onlyAvailable);
435    }
436
437    /**
438     * OpenCL profile string. Returns the profile name supported by the implementation. The profile name returned can be one of the following strings:
439     * <ul>
440     * <li>FULL_PROFILE if the implementation supports the OpenCL specification (functionality defined as part of the core specification and does not require any extensions to be supported).</li>
441     * <li>EMBEDDED_PROFILE if the implementation supports the OpenCL embedded profile. The embedded profile is defined to be a subset for each version of OpenCL. The embedded profile for OpenCL 1.0 is described in section 10.</li>
442     * </ul>
443     */
444    @InfoName("CL_PLATFORM_PROFILE")
445    public String getProfile() {
446        return infos.getString(getEntity(), CL_PLATFORM_PROFILE);
447    }
448
449    /**
450    OpenCL version string. Returns the OpenCL version supported by the implementation. This version string has the following format:
451    OpenCL&lt;space&gt;&lt;major_version.min or_version&gt;&lt;space&gt;&lt;platform- specific information&gt;
452    Last Revision Date: 5/16/09 Page 30
453    The major_version.minor_version value returned will be 1.0.
454     */
455    @InfoName("CL_PLATFORM_VERSION")
456    public String getVersion() {
457        return infos.getString(getEntity(), CL_PLATFORM_VERSION);
458    }
459
460    private double versionValue = Double.NaN;
461    private static final Pattern VERSION_PATTERN = Pattern.compile("OpenCL (\\d+\\.\\d+)\\b.*");
462    double getVersionValue() {
463        if (Double.isNaN(versionValue)) {
464                String versionString = getVersion();
465                Matcher matcher = VERSION_PATTERN.matcher(versionString);
466                if (matcher.matches()) {
467                        String str = matcher.group(1);
468                        versionValue = Double.parseDouble(str);
469                } else {
470                        log(Level.SEVERE, "Failed to parse OpenCL version: '" + versionString + "'");
471                }
472        }
473        return versionValue;
474    }
475    void requireMinVersionValue(String feature, double minValue) {
476        requireMinVersionValue(feature, minValue, Double.NaN);
477    }
478    private Set<String> featuresCheckedForVersion = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
479    void requireMinVersionValue(String feature, double minValue, double deprecationValue) {
480        double value = getVersionValue();
481        if (value < minValue) {
482                throw new CLVersionException(feature + " requires OpenCL version " + minValue +
483                        " (detected version is " + value + ")");
484        } else if (!Double.isNaN(deprecationValue) && featuresCheckedForVersion.add(feature)) {
485                Level level = null;
486                if (value < deprecationValue && JavaCL.verbose)
487                        level = Level.INFO;
488                else if (value >= deprecationValue)
489                        level = Level.WARNING;
490                if (level != null && shouldLog(level))
491                        log(level, feature + " is deprecated from OpenCL version " + deprecationValue +
492                        " (detected version is " + value + ")");
493        }
494    }
495
496    /**
497     * Platform name string.
498     */
499    @InfoName("CL_PLATFORM_NAME")
500    public String getName() {
501        return infos.getString(getEntity(), CL_PLATFORM_NAME);
502    }
503
504    /**
505     * Platform vendor string.
506     */
507    @InfoName("CL_PLATFORM_VENDOR")
508    public String getVendor() {
509        return infos.getString(getEntity(), CL_PLATFORM_VENDOR);
510    }
511
512    /**
513     * Returns a list of extension names <br>
514     * Extensions defined here must be supported by all devices associated with this platform.
515     */
516    @InfoName("CL_PLATFORM_EXTENSIONS")
517    public String[] getExtensions() {
518        if (extensions == null) {
519            extensions = new LinkedHashSet<String>(Arrays.asList(infos.getString(getEntity(), CL_PLATFORM_EXTENSIONS).split("\\s+")));
520        }
521        return extensions.toArray(new String[extensions.size()]);
522    }
523    private Set<String> extensions;
524
525    public boolean hasExtension(String name) {
526        getExtensions();
527        return extensions.contains(name.trim());
528    }
529
530    @InfoName("cl_nv_device_attribute_query")
531    public boolean isNVDeviceAttributeQuerySupported() {
532        return hasExtension("cl_nv_device_attribute_query");
533    }
534
535    @InfoName("cl_nv_compiler_options")
536    public boolean isNVCompilerOptionsSupported() {
537        return hasExtension("cl_nv_compiler_options");
538    }
539
540    @InfoName("cl_khr_byte_addressable_store")
541    public boolean isByteAddressableStoreSupported() {
542        return hasExtension("cl_khr_byte_addressable_store");
543    }
544
545    @InfoName("cl_khr_gl_sharing")
546    public boolean isGLSharingSupported() {
547        return hasExtension("cl_khr_gl_sharing") || hasExtension("cl_APPLE_gl_sharing");
548    }
549
550    /**
551     * Allows the implementation to release the resources allocated by the OpenCL compiler for this platform.
552     */
553    public void unloadPlatformCompiler() {
554        if (getVersionValue() < 1.2) {
555                        requireMinVersionValue("clUnloadCompiler", 1.1, 1.2);
556                        error(CL.clUnloadCompiler());
557                } else {
558                        requireMinVersionValue("clUnloadPlatformCompiler", 1.2);
559                        error(CL.clUnloadPlatformCompiler(getEntity()));
560                }
561    }
562
563}