Explorar el Código

implemented a cache for program objects

shamanDevel hace 9 años
padre
commit
a2df82b9e0

+ 1 - 1
jme3-core/src/main/java/com/jme3/opencl/Program.java

@@ -71,7 +71,7 @@ public abstract class Program extends AbstractOpenCLObject {
      * @see #build(java.lang.String) 
      */
 	public void build() throws KernelCompilationException {
-        build("", null);
+        build("", (Device[]) null);
     }
 
     /**

+ 214 - 0
jme3-core/src/main/java/com/jme3/opencl/ProgramCache.java

@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2009-2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.opencl;
+
+import com.jme3.system.JmeSystem;
+import com.jme3.util.BufferUtils;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implements a simple cache system for program objects.
+ * The program objects are saved persistently with {@link #saveToCache(java.lang.String, com.jme3.opencl.Program) }.
+ * On the next run, the stored programs can then be loaded
+ * with {@link #loadFromCache(java.lang.String, java.lang.String) }.
+ * <br>
+ * The programs are identified by a unique id. The following format is recommended:
+ * {@code id = <full name of the class using the program>.<unique identifier within that class>}.
+ * 
+ * @author shaman
+ */
+public class ProgramCache {
+    private static final Logger LOG = Logger.getLogger(ProgramCache.class.getName());
+    private static final String FILE_EXTENSION = ".clbin";
+    
+    private final Context context;
+    private final Device device;
+    private final File tmpFolder;
+
+    /**
+     * Creates a new program cache associated with the specified context and
+     * devices.
+     * The cached programs are built against the specified device and also
+     * only the binaries linked to that device are stored.
+     * @param context the OpenCL context
+     * @param device the OpenCL device
+     */
+    public ProgramCache(Context context, Device device) {
+        this.context = context;
+        this.device = device;
+        if (JmeSystem.isLowPermissions()) {
+            tmpFolder = null;
+        } else {
+            tmpFolder = JmeSystem.getStorageFolder();
+        }
+    }
+    
+    protected String getCleanFileName(String id) {
+        //http://stackoverflow.com/a/35591188/4053176
+        return id.replaceAll("[^a-zA-Z0-9.-]", "") + FILE_EXTENSION;
+    }
+
+    /**
+     * Creates a new program cache using the first device from the specified 
+     * context.
+     * @param context the context
+     * @see #ProgramCache(com.jme3.opencl.Context, com.jme3.opencl.Device) 
+     */
+    public ProgramCache(Context context) {
+        this(context, context.getDevices().get(0));
+    }
+
+    /**
+     * Loads the program from the cache and builds it against the current device.
+     * You can pass additional build arguments with the parameter {@code buildArgs}.
+     * <p>
+     * The cached program is identified by the specified id. 
+     * This id must be unique, otherwise collisions within the cache occur.
+     * Therefore, the following naming schema is recommended:
+     * {@code id = <full name of the class using the program>.<unique identifier within that class>}.
+     * <p>
+     * If the program can't be loaded, built or any other exception happened,
+     * {@code null} is returned.
+     * 
+     * @param id the unique identifier of this program
+     * @param buildArgs additional build arguments, can be {@code null}
+     * @return the loaded and built program, or {@code null}
+     * @see #saveToCache(java.lang.String, com.jme3.opencl.Program) 
+     */
+    public Program loadFromCache(String id, String buildArgs) {
+        if (tmpFolder == null) {
+            return null; //low permissions
+        }
+        //get file
+        File file = new File(tmpFolder, getCleanFileName(id));
+        if (!file.exists()) {
+            if (LOG.isLoggable(Level.FINE)) {
+                LOG.log(Level.FINE, "Cache file {0} does not exist", file.getAbsolutePath());
+            }
+            return null;
+        }
+        //load from file
+        ByteBuffer bb;
+        try {
+            byte[] bytes = Files.readAllBytes(file.toPath());
+            bb = BufferUtils.createByteBuffer(bytes);
+        } catch (IOException ex) {
+            LOG.log(Level.FINE, "Unable to read cache file", ex);
+            return null;
+        }
+        //create program
+        Program program;
+        try {
+            program = context.createProgramFromBinary(bb, device);
+        } catch (OpenCLException ex) {
+            LOG.log(Level.FINE, "Unable to create program from binary", ex);
+            return null;
+        }
+        //build program
+        try {
+            program.build(buildArgs, device);
+        } catch (OpenCLException ex) {
+            LOG.log(Level.FINE, "Unable to build program", ex);
+            return null;
+        }
+        //done
+        return program;
+    }
+    
+    /**
+     * Calls {@link #loadFromCache(java.lang.String, java.lang.String) }
+     * with the additional build arguments set to {@code ""}.
+     * @param id a unique identifier of the program
+     * @return the loaded and built program or {@code null} if this
+     * program could not be loaded from the cache
+     * @see #loadFromCache(java.lang.String, java.lang.String) 
+     */
+    public Program loadFromCache(String id) {
+        return loadFromCache(id, "");
+    }
+    
+    /**
+     * Saves the specified program in the cache.
+     * The parameter {@code id} denotes the name of the program. Under this id,
+     * the program is then loaded again by {@link #loadFromCache(java.lang.String, java.lang.String) }.
+     * <br>
+     * The id must be unique, otherwise collisions within the cache occur.
+     * Therefore, the following naming schema is recommended:
+     * {@code id = <full name of the class using the program>.<unique identifier within that class>}.
+     * 
+     * @param id the program id
+     * @param program the program to store in the cache
+     */
+    public void saveToCache(String id, Program program) {
+        if (tmpFolder == null) {
+            return; //low permissions
+        }
+        //get file
+        File file = new File(tmpFolder, getCleanFileName(id));
+        //get binaries
+        ByteBuffer bb;
+        try {
+            bb = program.getBinary(device);
+        } catch (UnsupportedOperationException | OpenCLException ex) {
+            LOG.log(Level.WARNING, "Unable to retrieve the program binaries", ex);
+            return;
+        }
+        byte[] bytes = new byte[bb.remaining()];
+        bb.get(bytes);
+        //save
+        try {
+            Files.write(file.toPath(), bytes);
+        } catch (IOException ex) {
+           LOG.log(Level.WARNING, "Unable to save program binaries to the cache", ex);
+        }
+    }
+    
+    /**
+     * Clears the cache.
+     * All saved program binaries are deleted.
+     */
+    public void clearCache() {
+        if (tmpFolder == null) {
+            return; //low permissions
+        }
+        for (File file : tmpFolder.listFiles()) {
+            if (file.isFile() && file.getName().endsWith(FILE_EXTENSION)) {
+                file.delete();
+            }
+        }
+    }
+}

+ 12 - 2
jme3-examples/src/main/java/jme3test/opencl/TestWriteToTexture.java

@@ -49,6 +49,8 @@ import java.util.logging.Logger;
  * This test class tests the capability to write to a GL texture from OpenCL.
  * Move the mouse around while pressing the left mouse key to modify the fractal.
  * 
+ * In addition, this test shows how to use {@link ProgramCache}.
+ * 
  * @author shaman
  */
 public class TestWriteToTexture extends SimpleApplication implements AnalogListener, ActionListener {
@@ -59,6 +61,7 @@ public class TestWriteToTexture extends SimpleApplication implements AnalogListe
     private int initCounter;
     private Context clContext;
     private CommandQueue clQueue;
+    private ProgramCache programCache;
     private Kernel kernel;
     private Vector2f C;
     private Image texCL;
@@ -121,9 +124,16 @@ public class TestWriteToTexture extends SimpleApplication implements AnalogListe
         clContext = context.getOpenCLContext();
         clQueue = clContext.createQueue();
         clQueue.register();
+        programCache = new ProgramCache(clContext);
         //create kernel
-        Program program = clContext.createProgramFromSourceFiles(assetManager, "jme3test/opencl/JuliaSet.cl");
-        program.build();
+        String cacheID = getClass().getName()+".Julia";
+        Program program = programCache.loadFromCache(cacheID);
+        if (program == null) {
+            LOG.info("Program not loaded from cache, create from sources instead");
+            program = clContext.createProgramFromSourceFiles(assetManager, "jme3test/opencl/JuliaSet.cl");
+            program.build();
+            programCache.saveToCache(cacheID, program);
+        }
         program.register();
         kernel = program.createKernel("JuliaSet");
         kernel.register();