public class OpenCLSettings {
	
	private final Context clContext;
	private final CommandQueue clCommandQueue;
	private final ProgramCache programCache;
	private final AssetManager assetManager;

	public OpenCLSettings(Context clContext, CommandQueue clCommandQueue, 
			ProgramCache programCache, AssetManager assetManager) {
		this.clContext = clContext;
		this.clCommandQueue = clCommandQueue;
		this.programCache = programCache!=null ? programCache : new ProgramCache();
		this.assetManager = assetManager;
	}

	public Context getClContext() {
		return clContext;
	}

	public CommandQueue getClCommandQueue() {
		return clCommandQueue;
	}

	public ProgramCache getProgramCache() {
		return programCache;
	}

	public AssetManager getAssetManager() {
		return assetManager;
	}

	@Override
	public int hashCode() {
		int hash = 3;
		hash = 43 * hash + Objects.hashCode(this.clContext);
		hash = 43 * hash + Objects.hashCode(this.clCommandQueue);
		return hash;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final OpenCLSettings other = (OpenCLSettings) obj;
		if (!Objects.equals(this.clContext, other.clContext)) {
			return false;
		}
		if (!Objects.equals(this.clCommandQueue, other.clCommandQueue)) {
			return false;
		}
		return true;
	}

} public class DebugTools {
	
	private final FluidSolver solver;
	
	private boolean flagCopyInitialized;
	private Buffer flagColorsBuffer;
	private Kernel flagCopyKernel;

	public DebugTools(FluidSolver solver) {
		this.solver = solver;
	}
	
	public SharedTexture createRealTexture2D(RenderManager renderManager) {
		com.jme3.opencl.Image.ImageDescriptor descriptor = new com.jme3.opencl.Image.ImageDescriptor(
				com.jme3.opencl.Image.ImageType.IMAGE_2D, solver.resolutionX, solver.resolutionY, 0, 0);
		SharedTexture tex = new SharedTexture(descriptor, Image.Format.Luminance32F);
		tex.initialize(renderManager, solver.clSettings.getClContext(), MemoryAccess.READ_WRITE);
		return tex;
	}
	
	public void fillTextureWithDensity2D(RealGrid density, SharedTexture realTexture) {
		CommandQueue cq = solver.clSettings.getClCommandQueue();
		realTexture.getCLImage().acquireImageForSharingNoEvent(cq);
		density.buffer.copyToImageAsync(cq, realTexture.getCLImage(), 0, new long[]{0,0,0}, new long[]{solver.resolutionX, solver.resolutionY,1}).release();
		realTexture.getCLImage().releaseImageForSharingNoEvent(cq);
	}
	
	public SharedTexture createFlagTexture2D(RenderManager renderManager) {
		com.jme3.opencl.Image.ImageDescriptor descriptor = new com.jme3.opencl.Image.ImageDescriptor(
				com.jme3.opencl.Image.ImageType.IMAGE_2D, solver.resolutionX, solver.resolutionY, 0, 0);
		SharedTexture tex = new SharedTexture(descriptor, Image.Format.RGBA8);
		tex.initialize(renderManager, solver.clSettings.getClContext(), MemoryAccess.READ_WRITE);
		return tex;
	}
	
	private void initFlagCopy() {
		if (flagCopyInitialized) {
			return;
		}
		
		String cacheID = DebugTools.class.getName()+"_FlagCopy";
		Program program = solver.clSettings.getProgramCache().loadFromCache(cacheID);
		if (program == null) {
			String source = 
				"#define L_INVSZ(i, vi, sz)	(vi).y = i / (sz).x; (vi).x = (i - (vi).y*(sz).x);\n" +
				"__kernel void CopyFlags(__global char* flagGrid, __global float4* colors, __write_only image2d_t image, int sizeX, int sizeY)\n" +
				"{\n" +
				"	int idx = get_global_id(0);\n" +
				"	int2 dim = (int2)(sizeX, sizeY);\n" +
				"	int2 pos;\n" +
				"	L_INVSZ(idx, pos, dim);\n" +
				"	char flag = flagGrid[idx];\n" +
				"	float4 color = colors[flag];\n" +
				"	write_imagef(image, pos, color);\n" +
				"}";
			program = solver.clSettings.getClContext().createProgramFromSourceCode(source);
			program.build();
			solver.clSettings.getProgramCache().saveToCache(cacheID, program);
		}
		program.register();
		flagCopyKernel = program.createKernel("CopyFlags");
		
		ByteBuffer bb = BufferUtils.createByteBuffer(256*4*4);
		FloatBuffer fb = bb.asFloatBuffer();
		EnumMap<FlagGrid.CellType, ColorRGBA> flagColors = new EnumMap<>(FlagGrid.CellType.class);
		flagColors.put(FlagGrid.CellType.TypeInflow, new ColorRGBA(0, 1, 0, 0.5f));
		flagColors.put(FlagGrid.CellType.TypeOutflow, new ColorRGBA(0, 0, 1, 0.5f));
		flagColors.put(FlagGrid.CellType.TypeObstacle, new ColorRGBA(1, 0, 0, 1));
		for (int i=0; i<256; ++i) {
			ColorRGBA col = new ColorRGBA(0, 0, 0, 0);
			for (EnumMap.Entry<FlagGrid.CellType, ColorRGBA> e : flagColors.entrySet()) {
				if ((i & e.getKey().value) != 0) {
					ColorRGBA c = e.getValue();
					float ac = c.a + (1-c.a)*col.a;
					col.set((c.a*c.r + (1-c.a)*col.a*col.r)/ac, (c.a*c.g + (1-c.a)*col.a*col.g)/ac, (c.a*c.b + (1-c.a)*col.a*col.b)/ac, ac);
				}
			}
			System.out.println("flag "+i+" ("+Integer.toBinaryString(i)+"): "+col);
			fb.put(col.r).put(col.g).put(col.b).put(col.a);
		}
		flagColorsBuffer = solver.clSettings.getClContext().createBuffer(256*4*4);
		flagColorsBuffer.write(solver.clSettings.getClCommandQueue(), bb);
		
		flagCopyInitialized = true;
	}
	
	public void fillTextureWithFlags2D(FlagGrid flags, SharedTexture flagTexture) {
		initFlagCopy();
		CommandQueue cq = solver.clSettings.getClCommandQueue();
		flagTexture.getCLImage().acquireImageForSharingNoEvent(cq);
		Kernel.WorkSize ws = new Kernel.WorkSize(solver.resolutionX * solver.resolutionY);
		flagCopyKernel.Run1NoEvent(cq, ws, flags.buffer, flagColorsBuffer, flagTexture.getCLImage(), solver.resolutionX, solver.resolutionY);
		flagTexture.getCLImage().releaseImageForSharingNoEvent(cq);
	}
	
	public void printGrid2D(RealGrid grid) {
		ByteBuffer bb = grid.buffer.map(solver.clSettings.getClCommandQueue(), MappingAccess.MAP_READ_ONLY);
		FloatBuffer fb = bb.asFloatBuffer();
		for (int y=0; y<solver.resolutionY; ++y) {
			for (int x=0; x<solver.resolutionX; ++x) {
				float v = fb.get();
				System.out.printf("%2.3f ", v);
			}
			System.out.println();
		}
		grid.buffer.unmap(solver.clSettings.getClCommandQueue(), bb);
	}
	
	public void printGrid2D(MACGrid grid) {
		ByteBuffer bb = grid.buffer.map(solver.clSettings.getClCommandQueue(), MappingAccess.MAP_READ_ONLY);
		FloatBuffer fb = bb.asFloatBuffer();
		for (int y=0; y<solver.resolutionY; ++y) {
			for (int x=0; x<solver.resolutionX; ++x) {
				int idx1 = x + y*(solver.resolutionX+1);
				int idx2 = (solver.resolutionX+1)*solver.resolutionY + x + y*solver.resolutionX;
				float vx = 0.5f * (fb.get(idx1) + fb.get(idx1+1));
				float vy = 0.5f * (fb.get(idx2) + fb.get(idx2+solver.resolutionX));
				System.out.printf("(%2.3f,%2.3f) ", vx, vy);
			}
			System.out.println();
		}
		grid.buffer.unmap(solver.clSettings.getClCommandQueue(), bb);
	}
} public class FlagGrid extends Grid {
	
	public static enum CellType { 
		TypeNone(0),
		TypeFluid(1),
		TypeObstacle(2),
		TypeEmpty(4),
		TypeInflow(8),
		TypeOutflow(16),
		TypeOpen(32),
		TypeStick(128);
				
		public final int value;

		private CellType(int value) {
			this.value = value;
		}
	
	};
	
	FlagGrid(FluidSolver solver) {
		super(solver.clSettings.getClContext().createBuffer(solver.getResolutionX() * solver.getResolutionY() * solver.getResolutionZ()), solver);
	}
	
	public void fill(CellType type) {
		CLBlas<Byte> blas = CLBlas.get(solver.clSettings, Byte.class);
		blas.fill(buffer, (byte) type.value);
	}
	
} public class FluidSolver {
	
	protected final OpenCLSettings clSettings;
	protected final int resolutionX;
	protected final int resolutionY;
	protected final int resolutionZ;
	protected final boolean twoD;
	
	/**
	 * Initializes the 3d version of the fluid solver.
	 * @param clSettings the OpenCL settings
	 * @param resolutionX the x-resolution
	 * @param resolutionY the y-resolution
	 * @param resolutionZ the z-resolution
	 */
	public FluidSolver(OpenCLSettings clSettings, int resolutionX, int resolutionY, int resolutionZ) {
		this.clSettings = clSettings;
		this.resolutionX = resolutionX;
		this.resolutionY = resolutionY;
		this.resolutionZ = resolutionZ;
		this.twoD = false;
	}

	/**
	 * Initializes the 2d version of the fluid solver.
	 * @param clSettings the OpenCL settings
	 * @param resolutionX the x-resolution
	 * @param resolutionY the y-resolution
	 */
	public FluidSolver(OpenCLSettings clSettings, int resolutionX, int resolutionY) {
		this.clSettings = clSettings;
		this.resolutionX = resolutionX;
		this.resolutionY = resolutionY;
		this.resolutionZ = 1;
		this.twoD = true;
	}
	
	/**
	 * Returns {@code true} if the solver runs in the 2D mode.
	 * @return {@code true} if 2D, {@code false} if 3D.
	 */
	public boolean is2D() {
		return twoD;
	}
	
	/**
	 * Returns {@code true} if the solver runs in the 3D mode.
	 * @return {@code true} if 3D, {@code false} if 2D.
	 */
	public boolean is3D() {
		return !twoD;
	}

	/**
	 * @return the x-resolution
	 */
	public int getResolutionX() {
		return resolutionX;
	}

	/**
	 * @return the y-resolution
	 */
	public int getResolutionY() {
		return resolutionY;
	}

	/**
	 * @return the z-resolution, 1 if 2D
	 */
	public int getResolutionZ() {
		return resolutionZ;
	}
	
	/**
	 * Creates a new real grid linked to this solver.
	 * @return the new real grid
	 */
	public RealGrid createRealGrid() {
		return new RealGrid(this);
	}
	
	/**
	 * Creates a new MAC grid or staggered grid linked to this solver.
	 * @return the new MAC grid
	 */
	public MACGrid createMACGrid() {
		return new MACGrid(this);
	}
	
	/**
	 * Creates a new flag grid linked to this solver.
	 * @return the new flag grid
	 */
	public FlagGrid createFlagGrid() {
		return new FlagGrid(this);
	}
	
} public abstract class Grid {
	
	protected Buffer buffer;
	protected final FluidSolver solver;

	public Grid(FluidSolver solver) {
		this.solver = solver;
	}

	public Grid(Buffer buffer, FluidSolver solver) {
		this.buffer = buffer;
		this.solver = solver;
	}

	public Buffer getBuffer() {
		return buffer;
	}

	public void setBuffer(Buffer buffer) {
		this.buffer = buffer;
	}

	public FluidSolver getSolver() {
		return solver;
	}

	public void swap(Grid other) {
		assert (this.getClass() == other.getClass());
		Buffer tmp = this.buffer;
		this.buffer = other.buffer;
		other.buffer = tmp;
	}
} public class MACGrid extends Grid {
	
	MACGrid(FluidSolver solver) {
		super(solver);
		long sizeX = (solver.getResolutionX()+1) * solver.getResolutionY() * solver.getResolutionZ();
		long sizeY = solver.getResolutionX() * (solver.getResolutionY()+1) * solver.getResolutionZ();
		long sizeZ = solver.getResolutionX() * solver.getResolutionY() * (solver.getResolutionZ()+1);
		buffer = solver.clSettings.getClContext().createBuffer(4 * (sizeX + sizeY + (solver.is2D() ? 0 : sizeZ)));
	}

	public void fill(Vector3f v) {
		CLBlas<Float> blas = CLBlas.get(solver.clSettings, Float.class);
		long sizeX = (solver.getResolutionX()+1) * solver.getResolutionY() * solver.getResolutionZ();
		long sizeY = solver.getResolutionX() * (solver.getResolutionY()+1) * solver.getResolutionZ();
		long sizeZ = solver.getResolutionX() * solver.getResolutionY() * (solver.getResolutionZ()+1);
		blas.fill(buffer, v.x, sizeX);
		blas.fill(buffer, v.y, sizeY, sizeX, 1);
		if (solver.is3D()) {
			blas.fill(buffer, v.z, sizeZ, sizeX + sizeY, 1);
		}
	}
} public class RealGrid extends Grid {
	
	RealGrid(FluidSolver solver) {
		super(solver.clSettings.getClContext().createBuffer(4 * solver.getResolutionX() * solver.getResolutionY() * solver.getResolutionZ()), solver);
	}
	
	RealGrid(FluidSolver solver, Buffer buffer) {
		super(buffer, solver);
	}
	
	public void fill(float value) {
		CLBlas<Float> blas = CLBlas.get(solver.clSettings, Float.class);
		blas.fill(buffer, value);
	}
} public class DebugContextFactory {
	
	/**
	 * clFinish is called after each kernel call.
	 */
	private static final int FLAG_FINISH_AFTER_KERNEL = 1;
	/**
	 * clFinish is called after each memory operation.
	 */
	private static final int FLAG_FINISH_AFTER_MEMOP = 2;
	/**
	 * clFinish is called after each mem/image acquiring/releasing.
	 */
	private static final int FLAG_FINISH_AFTER_ACQUIRE = 4;
	/**
	 * All tests are performed.
	 */
	private static final int FLAG_ALL = FLAG_FINISH_AFTER_KERNEL | FLAG_FINISH_AFTER_MEMOP | FLAG_FINISH_AFTER_ACQUIRE;

	private static final Logger LOG = Logger.getLogger(DebugContextFactory.class.getName());
	
	public static Context createDebugContext(Context delegate) {
		return createDebugContext(delegate, FLAG_ALL);
	}
	public static Context createDebugContext(Context delegate, int flags) {
		LOG.log(Level.INFO, "running debug context that delegates to {0}", delegate);
		return new DebugContext(delegate, flags);
	}
	
	private static void throwWrongType(Object obj) {
		throw new IllegalArgumentException("argument "+obj+" ("+obj.getClass()+") was not created by the debug wrapper");
	}
	
	private static class DebugContext extends Context {
		private final Context delegate;
		private final int flags;

		public DebugContext(Context delegate, int flags) {
			super(null);
			this.delegate = delegate;
			this.flags = flags;
		}

		@Override
		public Context register() {
			delegate.register();
			return this;
		}

		@Override
		public List<? extends Device> getDevices() {
			return delegate.getDevices();
		}

		@Override
		public CommandQueue createQueue() {
			return new DebugCommandQueue(delegate.createQueue(), flags);
		}

		@Override
		public CommandQueue createQueue(Device device) {
			return new DebugCommandQueue(delegate.createQueue(device), flags);
		}

		@Override
		public Buffer createBuffer(long l, MemoryAccess ma) {
			return new DebugBuffer(delegate.createBuffer(l, ma), flags, false);
		}

		@Override
		public Buffer createBuffer(long l) {
			return new DebugBuffer(delegate.createBuffer(l), flags, false);
		}

		@Override
		public Buffer createBufferFromHost(ByteBuffer bb, MemoryAccess ma) {
			return new DebugBuffer(delegate.createBufferFromHost(bb, ma), flags, false);
		}

		@Override
		public Buffer createBufferFromHost(ByteBuffer bb) {
			return new DebugBuffer(delegate.createBufferFromHost(bb), flags, false);
		}

		@Override
		public Image createImage(MemoryAccess ma, Image.ImageFormat i, Image.ImageDescriptor id) {
			return new DebugImage(delegate.createImage(ma, i, id), flags, false);
		}

		@Override
		public Image.ImageFormat[] querySupportedFormats(MemoryAccess ma, Image.ImageType it) {
			return delegate.querySupportedFormats(ma, it);
		}

		@Override
		public Buffer bindVertexBuffer(VertexBuffer vb, MemoryAccess ma) {
			return new DebugBuffer(delegate.bindVertexBuffer(vb, ma), flags, true);
		}

		@Override
		public Image bindImage(com.jme3.texture.Image image, Texture.Type type, int i, MemoryAccess ma) {
			return new DebugImage(delegate.bindImage(image, type, i, ma), flags, true);
		}

		@Override
		public Image bindImage(Texture txtr, int i, MemoryAccess ma) {
			return new DebugImage(delegate.bindImage(txtr, i, ma), flags, true);
		}

		@Override
		public Image bindImage(Texture txtr, MemoryAccess ma) {
			return new DebugImage(delegate.bindImage(txtr, ma), flags, true);
		}

		@Override
		public Image bindRenderBuffer(FrameBuffer.RenderBuffer rb, MemoryAccess ma) {
			return new DebugImage(delegate.bindRenderBuffer(rb, ma), flags, true);
		}

		@Override
		public Program createProgramFromSourceCode(String string) {
			return new DebugProgram(delegate.createProgramFromSourceCode(string), flags);
		}

		@Override
		public Program createProgramFromSourceCodeWithDependencies(String string, AssetManager am) {
			return new DebugProgram(delegate.createProgramFromSourceCodeWithDependencies(string, am), flags);
		}

		@Override
		public Program createProgramFromSourceFilesWithInclude(AssetManager am, String string, String... strings) {
			return new DebugProgram(delegate.createProgramFromSourceFilesWithInclude(am, string, strings), flags);
		}

		@Override
		public Program createProgramFromSourceFilesWithInclude(AssetManager am, String string, List<String> list) {
			return new DebugProgram(delegate.createProgramFromSourceFilesWithInclude(am, string, list), flags);
		}

		@Override
		public Program createProgramFromSourceFiles(AssetManager am, String... strings) {
			return new DebugProgram(delegate.createProgramFromSourceFiles(am, strings), flags);
		}

		@Override
		public Program createProgramFromSourceFiles(AssetManager am, List<String> list) {
			return new DebugProgram(delegate.createProgramFromSourceFiles(am, list), flags);
		}

		@Override
		public Program createProgramFromBinary(ByteBuffer bb, Device device) {
			return new DebugProgram(delegate.createProgramFromBinary(bb, device), flags);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}

		@Override
		public void release() {
			delegate.release();
		}

		@Override
		public ObjectReleaser getReleaser() {
			return delegate.getReleaser();
		}

		@Override
		protected Image bindPureRenderBuffer(FrameBuffer.RenderBuffer rb, MemoryAccess ma) {
			throw new UnsupportedOperationException("Not supported yet.");
		}
		
	}
	
	private static class DebugCommandQueue extends CommandQueue {
		private final CommandQueue delegate;
		private final int flags;

		public DebugCommandQueue(CommandQueue delegate, int flags) {
			super(null, null);
			this.delegate = delegate;
			this.flags = flags;
		}

		@Override
		public CommandQueue register() {
			delegate.register();
			return this;
		}

		@Override
		public Device getDevice() {
			return delegate.getDevice();
		}

		@Override
		public void flush() {
			delegate.flush();
		}

		@Override
		public void finish() {
			delegate.finish();
		}

		@Override
		public void release() {
			delegate.release();
		}

		@Override
		public ObjectReleaser getReleaser() {
			return delegate.getReleaser();
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}
		
	}
	
	private static class DebugEvent extends Event {
		private final Event delegate;
		private final int flags;

		public DebugEvent(Event delegate, int flags) {
			super(null);
			this.delegate = delegate;
			this.flags = flags;
		}

		@Override
		public Event register() {
			delegate.register();
			return this;
		}

		@Override
		public void waitForFinished() {
			delegate.waitForFinished();
		}

		@Override
		public boolean isCompleted() {
			return delegate.isCompleted();
		}

		@Override
		public void release() {
			delegate.release();
		}

		@Override
		public ObjectReleaser getReleaser() {
			return delegate.getReleaser();
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}
		
	}
	
	private static class DebugBuffer extends Buffer {
		private final Buffer delegate;
		private final int flags;
		private final boolean shared;
		private boolean aquired;

		public DebugBuffer(Buffer delegate, int flags, boolean shared) {
			super(null);
			this.delegate = delegate;
			this.flags = flags;
			this.shared = shared;
		}

		private void checkAccess() {
			if (shared && !aquired) {
				throw new IllegalStateException("shared buffer is not aquired");
			}
		}
		
		@Override
		public Buffer register() {
			delegate.register();
			return this;
		}

		@Override
		public long getSize() {
			return delegate.getSize();
		}

		@Override
		public MemoryAccess getMemoryAccessFlags() {
			return delegate.getMemoryAccessFlags();
		}

		@Override
		public void read(CommandQueue cq, ByteBuffer bb, long l, long l1) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.read(((DebugCommandQueue) cq).delegate, bb, l, l1);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public void read(CommandQueue cq, ByteBuffer bb, long l) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.read(((DebugCommandQueue) cq).delegate, bb, l);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public void read(CommandQueue cq, ByteBuffer bb) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.read(((DebugCommandQueue) cq).delegate, bb);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public Event readAsync(CommandQueue cq, ByteBuffer bb, long l, long l1) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.readAsync(((DebugCommandQueue) cq).delegate, bb, l, l1), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event readAsync(CommandQueue cq, ByteBuffer bb, long l) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.readAsync(((DebugCommandQueue) cq).delegate, bb, l), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event readAsync(CommandQueue cq, ByteBuffer bb) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.readAsync(((DebugCommandQueue) cq).delegate, bb), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public void write(CommandQueue cq, ByteBuffer bb, long l, long l1) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.write(((DebugCommandQueue) cq).delegate, bb, l, l1);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public void write(CommandQueue cq, ByteBuffer bb, long l) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.write(((DebugCommandQueue) cq).delegate, bb, l);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public void write(CommandQueue cq, ByteBuffer bb) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.write(((DebugCommandQueue) cq).delegate, bb);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public Event writeAsync(CommandQueue cq, ByteBuffer bb, long l, long l1) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.writeAsync(((DebugCommandQueue) cq).delegate, bb, l, l1), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event writeAsync(CommandQueue cq, ByteBuffer bb, long l) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.writeAsync(((DebugCommandQueue) cq).delegate, bb, l), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event writeAsync(CommandQueue cq, ByteBuffer bb) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.writeAsync(((DebugCommandQueue) cq).delegate, bb), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public void copyTo(CommandQueue cq, Buffer buffer, long l, long l1, long l2) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof DebugBuffer)) throwWrongType(buffer);
			((DebugBuffer) buffer).checkAccess();
			delegate.copyTo(((DebugCommandQueue) cq).delegate, ((DebugBuffer) buffer).delegate, l, l1, l2);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public void copyTo(CommandQueue cq, Buffer buffer, long l) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof DebugBuffer)) throwWrongType(buffer);
			((DebugBuffer) buffer).checkAccess();
			delegate.copyTo(((DebugCommandQueue) cq).delegate, ((DebugBuffer) buffer).delegate, l);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public void copyTo(CommandQueue cq, Buffer buffer) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof DebugBuffer)) throwWrongType(buffer);
			((DebugBuffer) buffer).checkAccess();
			delegate.copyTo(((DebugCommandQueue) cq).delegate, ((DebugBuffer) buffer).delegate);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public Event copyToAsync(CommandQueue cq, Buffer buffer, long l, long l1, long l2) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof DebugBuffer)) throwWrongType(buffer);
			((DebugBuffer) buffer).checkAccess();
			Event e = new DebugEvent(delegate.copyToAsync(((DebugCommandQueue) cq).delegate, ((DebugBuffer) buffer).delegate, l, l1, l2), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event copyToAsync(CommandQueue cq, Buffer buffer, long l) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof DebugBuffer)) throwWrongType(buffer);
			((DebugBuffer) buffer).checkAccess();
			Event e = new DebugEvent(delegate.copyToAsync(((DebugCommandQueue) cq).delegate, ((DebugBuffer) buffer).delegate, l), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event copyToAsync(CommandQueue cq, Buffer buffer) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof DebugBuffer)) throwWrongType(buffer);
			((DebugBuffer) buffer).checkAccess();
			Event e = new DebugEvent(delegate.copyToAsync(((DebugCommandQueue) cq).delegate, ((DebugBuffer) buffer).delegate), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public ByteBuffer map(CommandQueue cq, long l, long l1, MappingAccess ma) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			ByteBuffer b = delegate.map(((DebugCommandQueue) cq).delegate, l, l1, ma);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return b;
		}

		@Override
		public ByteBuffer map(CommandQueue cq, long l, MappingAccess ma) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			ByteBuffer b = delegate.map(((DebugCommandQueue) cq).delegate, l, ma);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return b;
		}

		@Override
		public ByteBuffer map(CommandQueue cq, MappingAccess ma) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			ByteBuffer b = delegate.map(((DebugCommandQueue) cq).delegate, ma);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return b;
		}

		@Override
		public void unmap(CommandQueue cq, ByteBuffer bb) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.unmap(((DebugCommandQueue) cq).delegate, bb);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public AsyncMapping mapAsync(CommandQueue cq, long l, long l1, MappingAccess ma) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			AsyncMapping m = delegate.mapAsync(((DebugCommandQueue) cq).delegate, l, l1, ma);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return m;
		}

		@Override
		public AsyncMapping mapAsync(CommandQueue cq, long l, MappingAccess ma) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			AsyncMapping m = delegate.mapAsync(((DebugCommandQueue) cq).delegate, l, ma);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return m;
		}

		@Override
		public AsyncMapping mapAsync(CommandQueue cq, MappingAccess ma) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			AsyncMapping m = delegate.mapAsync(((DebugCommandQueue) cq).delegate, ma);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return m;
		}

		@Override
		public Event fillAsync(CommandQueue cq, ByteBuffer bb, long l, long l1) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.fillAsync(((DebugCommandQueue) cq).delegate, bb, l, l1), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event copyToImageAsync(CommandQueue cq, Image image, long l, long[] longs, long[] longs1) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!(image instanceof DebugImage)) throwWrongType(image);
			((DebugImage) image).checkAccess();
			Event e = new DebugEvent(delegate.copyToImageAsync(((DebugCommandQueue) cq).delegate, ((DebugImage) image).delegate, l, longs, longs1), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event acquireBufferForSharingAsync(CommandQueue cq) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!shared) {
				throw new IllegalStateException("attempt to acquire a buffer that is not shared");
			} else {
				if (aquired) {
					throw new IllegalStateException("buffer already acquired");
				}
			}
			aquired = true;
			Event e = new DebugEvent(delegate.acquireBufferForSharingAsync(((DebugCommandQueue) cq).delegate), flags);
			if ((flags & FLAG_FINISH_AFTER_ACQUIRE)!=0) cq.finish();
			return e;
		}

		@Override
		public void acquireBufferForSharingNoEvent(CommandQueue cq) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!shared) {
				throw new IllegalStateException("attempt to acquire a buffer that is not shared");
			} else {
				if (aquired) {
					throw new IllegalStateException("buffer already acquired");
				}
			}
			aquired = true;
			delegate.acquireBufferForSharingNoEvent(((DebugCommandQueue) cq).delegate);
			if ((flags & FLAG_FINISH_AFTER_ACQUIRE)!=0) cq.finish();
		}

		@Override
		public Event releaseBufferForSharingAsync(CommandQueue cq) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!shared) {
				throw new IllegalStateException("attempt to release a buffer that is not shared");
			} else {
				if (!aquired) {
					throw new IllegalStateException("buffer already released");
				}
			}
			aquired = false;
			Event e = new DebugEvent(delegate.releaseBufferForSharingAsync(((DebugCommandQueue) cq).delegate), flags);
			if ((flags & FLAG_FINISH_AFTER_ACQUIRE)!=0) cq.finish();
			return e;
		}

		@Override
		public void releaseBufferForSharingNoEvent(CommandQueue cq) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!shared) {
				throw new IllegalStateException("attempt to release a buffer that is not shared");
			} else {
				if (!aquired) {
					throw new IllegalStateException("buffer already released");
				}
			}
			aquired = false;
			delegate.releaseBufferForSharingNoEvent(((DebugCommandQueue) cq).delegate);
			if ((flags & FLAG_FINISH_AFTER_ACQUIRE)!=0) cq.finish();
		}

		@Override
		public String toString() {
			return delegate.toString();
		}

		@Override
		public void release() {
			delegate.release();
		}

		@Override
		public ObjectReleaser getReleaser() {
			return delegate.getReleaser();
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}
		
		
	}
	
	private static class DebugImage extends Image {
		private final Image delegate;
		private final int flags;
		private final boolean shared;
		private boolean aquired;
		
		public DebugImage(Image delegate, int flags, boolean shared) {
			super(null);
			this.delegate = delegate;
			this.flags = flags;
			this.shared = shared;
		}
		
		private void checkAccess() {
			if (shared && !aquired) {
				throw new IllegalStateException("shared buffer is not aquired");
			}
		}

		@Override
		public Image register() {
			delegate.register();
			return this;
		}

		@Override
		public long getWidth() {
			return delegate.getWidth();
		}

		@Override
		public long getHeight() {
			return delegate.getHeight();
		}

		@Override
		public long getDepth() {
			return delegate.getDepth();
		}

		@Override
		public long getRowPitch() {
			return delegate.getRowPitch();
		}

		@Override
		public long getSlicePitch() {
			return delegate.getSlicePitch();
		}

		@Override
		public long getArraySize() {
			return delegate.getArraySize();
		}

		@Override
		public ImageFormat getImageFormat() {
			return delegate.getImageFormat();
		}

		@Override
		public ImageType getImageType() {
			return delegate.getImageType();
		}

		@Override
		public int getElementSize() {
			return delegate.getElementSize();
		}

		@Override
		public void readImage(CommandQueue cq, ByteBuffer bb, long[] longs, long[] longs1, long l, long l1) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.readImage(((DebugCommandQueue) cq).delegate, bb, longs, longs1, l, l1);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public Event readImageAsync(CommandQueue cq, ByteBuffer bb, long[] longs, long[] longs1, long l, long l1) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.readImageAsync(((DebugCommandQueue) cq).delegate, bb, longs, longs1, l, l1), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public void writeImage(CommandQueue cq, ByteBuffer bb, long[] longs, long[] longs1, long l, long l1) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.writeImage(((DebugCommandQueue) cq).delegate, bb, longs, longs1, l, l1);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public Event writeImageAsync(CommandQueue cq, ByteBuffer bb, long[] longs, long[] longs1, long l, long l1) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.writeImageAsync(((DebugCommandQueue) cq).delegate, bb, longs, longs1, l, l1), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public void copyTo(CommandQueue cq, Image image, long[] longs, long[] longs1, long[] longs2) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!(image instanceof DebugImage)) throwWrongType(image);
			((DebugImage) image).checkAccess();
			delegate.copyTo(((DebugCommandQueue) cq).delegate, ((DebugImage) image).delegate, longs, longs1, longs2);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public Event copyToAsync(CommandQueue cq, Image image, long[] longs, long[] longs1, long[] longs2) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!(image instanceof DebugImage)) throwWrongType(image);
			((DebugImage) image).checkAccess();
			Event e = new DebugEvent(delegate.copyToAsync(((DebugCommandQueue) cq).delegate, ((DebugImage) image).delegate, longs, longs1, longs2), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public ImageMapping map(CommandQueue cq, long[] longs, long[] longs1, MappingAccess ma) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			ImageMapping m = delegate.map(((DebugCommandQueue) cq).delegate, longs, longs1, ma);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return m;
		}

		@Override
		public ImageMapping mapAsync(CommandQueue cq, long[] longs, long[] longs1, MappingAccess ma) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			ImageMapping m = delegate.mapAsync(((DebugCommandQueue) cq).delegate, longs, longs1, ma);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return m;
		}

		@Override
		public void unmap(CommandQueue cq, ImageMapping im) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.unmap(((DebugCommandQueue) cq).delegate, im);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
		}

		@Override
		public Event fillAsync(CommandQueue cq, long[] longs, long[] longs1, ColorRGBA crgba) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.fillAsync(((DebugCommandQueue) cq).delegate, longs, longs1, crgba), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event fillAsync(CommandQueue cq, long[] longs, long[] longs1, int[] ints) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.fillAsync(((DebugCommandQueue) cq).delegate, longs, longs1, ints), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event copyToBufferAsync(CommandQueue cq, Buffer buffer, long[] longs, long[] longs1, long l) {
			checkAccess();
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof DebugBuffer)) throwWrongType(buffer);
			((DebugBuffer) buffer).checkAccess();
			Event e = new DebugEvent(delegate.copyToBufferAsync(((DebugCommandQueue) cq).delegate, ((DebugBuffer) buffer).delegate, longs, longs1, l), flags);
			if ((flags & FLAG_FINISH_AFTER_MEMOP)!=0) cq.finish();
			return e;
		}

		@Override
		public Event acquireImageForSharingAsync(CommandQueue cq) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!shared) {
				throw new IllegalStateException("attempt to acquire an image that is not shared");
			} else {
				if (aquired) {
					throw new IllegalStateException("image already acquired");
				}
			}
			aquired = true;
			Event e = new DebugEvent(delegate.acquireImageForSharingAsync(((DebugCommandQueue) cq).delegate), flags);
			if ((flags & FLAG_FINISH_AFTER_ACQUIRE)!=0) cq.finish();
			return e;
		}

		@Override
		public void acquireImageForSharingNoEvent(CommandQueue cq) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!shared) {
				throw new IllegalStateException("attempt to acquire an image that is not shared");
			} else {
				if (aquired) {
					throw new IllegalStateException("image already acquired");
				}
			}
			aquired = true;
			delegate.acquireImageForSharingNoEvent(((DebugCommandQueue) cq).delegate);
			if ((flags & FLAG_FINISH_AFTER_ACQUIRE)!=0) cq.finish();
		}

		@Override
		public Event releaseImageForSharingAsync(CommandQueue cq) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!shared) {
				throw new IllegalStateException("attempt to release an image that is not shared");
			} else {
				if (!aquired) {
					throw new IllegalStateException("image already released");
				}
			}
			aquired = false;
			Event e = new DebugEvent(delegate.releaseImageForSharingAsync(((DebugCommandQueue) cq).delegate), flags);
			if ((flags & FLAG_FINISH_AFTER_ACQUIRE)!=0) cq.finish();
			return e;
		}

		@Override
		public void releaseImageForSharingNoEvent(CommandQueue cq) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			if (!shared) {
				throw new IllegalStateException("attempt to release an image that is not shared");
			} else {
				if (!aquired) {
					throw new IllegalStateException("image already released");
				}
			}
			aquired = false;
			delegate.releaseImageForSharingNoEvent(((DebugCommandQueue) cq).delegate);
			if ((flags & FLAG_FINISH_AFTER_ACQUIRE)!=0) cq.finish();
		}

		@Override
		public String toString() {
			return delegate.toString();
		}

		@Override
		public void release() {
			delegate.release();
		}

		@Override
		public ObjectReleaser getReleaser() {
			return delegate.getReleaser();
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}
			
	}
	
	private static class DebugProgram extends Program {
		private final Program delegate;
		private final int flags;

		public DebugProgram(Program delegate, int flags) {
			super(null);
			this.delegate = delegate;
			this.flags = flags;
		}

		@Override
		public Program register() {
			delegate.register();
			return this;
		}

		@Override
		public void build(String string, Device... devices) throws KernelCompilationException {
			delegate.build(string, devices);
		}

		@Override
		public void build() throws KernelCompilationException {
			delegate.build();
		}

		@Override
		public Kernel createKernel(String string) {
			return new DebugKernel(delegate.createKernel(string), flags);
		}

		@Override
		public Kernel[] createAllKernels() {
			Kernel[] kx = delegate.createAllKernels();
			Kernel[] dkx = new Kernel[kx.length];
			for (int i=0; i<kx.length; ++i) {
				dkx[i] = new DebugKernel(kx[i], flags);
			}
			return dkx;
		}

		@Override
		public ByteBuffer getBinary(Device device) {
			return delegate.getBinary(device);
		}

		@Override
		public void release() {
			delegate.release();
		}

		@Override
		public ObjectReleaser getReleaser() {
			return delegate.getReleaser();
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}
		
	}
	
	private static class DebugKernel extends Kernel {
		private final Kernel delegate;
		private final int flags;

		public DebugKernel(Kernel delegate, int flags) {
			super(null);
			this.delegate = delegate;
			this.flags = flags;
		}

		@Override
		public Kernel register() {
			delegate.register();
			return this;
		}

		@Override
		public String getName() {
			return delegate.getName();
		}

		@Override
		public int getArgCount() {
			return delegate.getArgCount();
		}

		@Override
		public WorkSize getGlobalWorkSize() {
			return delegate.getGlobalWorkSize();
		}

		@Override
		public void setGlobalWorkSize(WorkSize ws) {
			delegate.setGlobalWorkSize(ws);
		}

		@Override
		public void setGlobalWorkSize(int i) {
			delegate.setGlobalWorkSize(i);
		}

		@Override
		public void setGlobalWorkSize(int i, int i1) {
			delegate.setGlobalWorkSize(i, i1);
		}

		@Override
		public void setGlobalWorkSize(int i, int i1, int i2) {
			delegate.setGlobalWorkSize(i, i1, i2);
		}

		@Override
		public WorkSize getWorkGroupSize() {
			return delegate.getWorkGroupSize();
		}

		@Override
		public void setWorkGroupSize(WorkSize ws) {
			delegate.setWorkGroupSize(ws);
		}

		@Override
		public void setWorkGroupSize(int i) {
			delegate.setWorkGroupSize(i);
		}

		@Override
		public void setWorkGroupSize(int i, int i1) {
			delegate.setWorkGroupSize(i, i1);
		}

		@Override
		public void setWorkGroupSdize(int i, int i1, int i2) {
			delegate.setWorkGroupSdize(i, i1, i2);
		}

		@Override
		public void setWorkGroupSizeToNull() {
			delegate.setWorkGroupSizeToNull();
		}

		@Override
		public long getMaxWorkGroupSize(Device device) {
			return delegate.getMaxWorkGroupSize(device);
		}

		@Override
		public void setArg(int i, LocalMemPerElement lmpe) {
			delegate.setArg(i, lmpe);
		}

		@Override
		public void setArg(int i, LocalMem lm) {
			delegate.setArg(i, lm);
		}

		@Override
		public void setArg(int i, Buffer buffer) {
			if (!(buffer instanceof DebugBuffer)) throwWrongType(buffer);
			((DebugBuffer) buffer).checkAccess();
			delegate.setArg(i, ((DebugBuffer) buffer).delegate);
		}

		@Override
		public void setArg(int i, Image image) {
			if (!(image instanceof DebugImage)) throwWrongType(image);
			((DebugImage) image).checkAccess();
			delegate.setArg(i, ((DebugImage) image).delegate);
		}

		@Override
		public void setArg(int i, byte b) {
			delegate.setArg(i, b);
		}

		@Override
		public void setArg(int i, short s) {
			delegate.setArg(i, s);
		}

		@Override
		public void setArg(int i, int i1) {
			delegate.setArg(i, i1);
		}

		@Override
		public void setArg(int i, long l) {
			delegate.setArg(i, l);
		}

		@Override
		public void setArg(int i, float f) {
			delegate.setArg(i, f);
		}

		@Override
		public void setArg(int i, double d) {
			delegate.setArg(i, d);
		}

		@Override
		public void setArg(int i, Vector2f vctrf) {
			delegate.setArg(i, vctrf);
		}

		@Override
		public void setArg(int i, Vector4f vctrf) {
			delegate.setArg(i, vctrf);
		}

		@Override
		public void setArg(int i, Quaternion qtrn) {
			delegate.setArg(i, qtrn);
		}

		@Override
		public void setArg(int i, Matrix4f mtrxf) {
			delegate.setArg(i, mtrxf);
		}

		@Override
		public void setArg(int i, Matrix3f mtrxf) {
			delegate.setArg(i, mtrxf);
		}

		@Override
		public void setArg(int i, ByteBuffer bb, long l) {
			delegate.setArg(i, bb, l);
		}

		@Override
		public void setArg(int i, Object o) {
			delegate.setArg(i, o);
		}

		@Override
		public Event Run(CommandQueue cq) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.Run(((DebugCommandQueue) cq).delegate), flags);
			if ((flags & FLAG_FINISH_AFTER_KERNEL)!=0) cq.finish();
			return e;
		}

		@Override
		public void RunNoEvent(CommandQueue cq) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.RunNoEvent(((DebugCommandQueue) cq).delegate);
			if ((flags & FLAG_FINISH_AFTER_KERNEL)!=0) cq.finish();
		}

		private Object[] unwrap(Object[] os) {
			Object[] os2 = new Object[os.length];
			for (int i=0; i<os.length; ++i) {
				if (os[i] instanceof Buffer) {
					if (!(os[i] instanceof DebugBuffer)) throwWrongType(os[i]);
					((DebugBuffer) os[i]).checkAccess();
					os2[i] = ((DebugBuffer) os[i]).delegate;
				} else if (os[i] instanceof Image) {
					if (!(os[i] instanceof DebugImage)) throwWrongType(os[i]);
					((DebugImage) os[i]).checkAccess();
					os2[i] = ((DebugImage) os[i]).delegate;
				} else {
					os2[i] = os[i];
				}
			}
			return os2;
		}
		
		@Override
		public Event Run1(CommandQueue cq, WorkSize ws, Object... os) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.Run1(((DebugCommandQueue) cq).delegate, ws, unwrap(os)), flags);
			if ((flags & FLAG_FINISH_AFTER_KERNEL)!=0) cq.finish();
			return e;
		}

		@Override
		public void Run1NoEvent(CommandQueue cq, WorkSize ws, Object... os) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.Run1NoEvent(((DebugCommandQueue) cq).delegate, ws, unwrap(os));
			if ((flags & FLAG_FINISH_AFTER_KERNEL)!=0) cq.finish();
		}

		@Override
		public Event Run2(CommandQueue cq, WorkSize ws, WorkSize ws1, Object... os) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			Event e = new DebugEvent(delegate.Run2(((DebugCommandQueue) cq).delegate, ws, ws1, unwrap(os)), flags);
			if ((flags & FLAG_FINISH_AFTER_KERNEL)!=0) cq.finish();
			return e;
		}

		@Override
		public void Run2NoEvent(CommandQueue cq, WorkSize ws, WorkSize ws1, Object... os) {
			if (!(cq instanceof DebugCommandQueue)) throwWrongType(cq);
			delegate.Run2NoEvent(((DebugCommandQueue) cq).delegate, ws, ws1, unwrap(os));
			if ((flags & FLAG_FINISH_AFTER_KERNEL)!=0) cq.finish();
		}

		@Override
		public String toString() {
			return delegate.toString();
		}

		@Override
		public void release() {
			delegate.release();
		}

		@Override
		public ObjectReleaser getReleaser() {
			return delegate.getReleaser();
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}
		
	}
} public class LoggingContextFactory {
	/**
	 * The logger, everything is reported over it.
	 */
	private static final Logger LOG = Logger.getLogger(LoggingContextFactory.class.getName());
	
	/**
	 * Kernel calls are logged.
	 */
	public static final int FLAG_LOG_KERNEL = 1;
	/**
	 * Memory operations are logged.
	 */
	public static final int FLAG_LOG_MEMOP = 2;
	/**
	 * Acquiring+releasing of shared resources are logged.
	 */
	public static final int FLAG_LOG_ACQUIRE = 4;
	/**
	 * Object creation is logged.
	 */
	public static final int FLAG_LOG_CREATION = 8;
	/**
	 * Object deletion is logged.
	 */
	public static final int FLAG_LOG_DELETION = 16;
	/**
	 * Registering an object for automatic garbage collection is logged.
	 */
	public static final int FLAG_LOG_REGISTER = 32;
	/**
	 * Logs calls to clFinish() and clFlush().
	 */
	public static final int FLAG_LOG_FINISH = 64;
	/**
	 * Logs event functions.
	 */
	public static final int FLAG_LOG_EVENT = 128;
	
	public static final int FLAG_LOG_ALL = 0xffffffff;
	
	public static Context createLoggingContext(Context delegate, int flags) {
		return new LoggingContext(delegate, flags);
	}
	
	public static Context createLoggingContext(Context delegate) {
		return new LoggingContext(delegate, FLAG_LOG_ALL);
	}
	
	private static void throwWrongType(Object obj) {
		throw new IllegalArgumentException("argument "+obj+" ("+obj.getClass()+") was not created by the longging wrapper");
	}
	
	private static class LoggingReleaser implements OpenCLObject.ObjectReleaser {
		private final OpenCLObject.ObjectReleaser delegate;
		private final String objName;

		public LoggingReleaser(String objName, OpenCLObject.ObjectReleaser delegate) {
			this.objName = objName;
			this.delegate = delegate;
		}
		
		@Override
		public void release() {
			if (objName != null) LOG.log(Level.INFO, "release {0}", objName);
			delegate.release();
		}
		
	}
	
	private static class LoggingContext extends Context {
		private final Context delegate;
		private final int flags;

		public LoggingContext(Context delegate, int flags) {
			super(new LoggingReleaser(((flags & FLAG_LOG_DELETION) != 0) ? delegate.toString() : null, delegate.getReleaser()));
			this.delegate = delegate;
			this.flags = flags;
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.info("DebugContext created");
		}

		@Override
		public Context register() {
			delegate.register();
			if ((flags & FLAG_LOG_REGISTER)!=0) LOG.log(Level.INFO, "{0} registered", this);
			return this;
		}

		@Override
		public List<? extends Device> getDevices() {
			return delegate.getDevices();
		}

		@Override
		public CommandQueue createQueue() {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: create command queue with the default device", this);
			return new LoggingCommandQueue(delegate.createQueue(), flags);
		}

		@Override
		public CommandQueue createQueue(Device device) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: create command queue with {1}", new Object[]{this, device});
			return new LoggingCommandQueue(delegate.createQueue(device), flags);
		}

		@Override
		public Buffer createBuffer(long l, MemoryAccess ma) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: create buffer of size {1}B and memory access {2}", new Object[]{this, l, ma});
			return new LoggingBuffer(delegate.createBuffer(l, ma), flags);
		}

		@Override
		public Buffer createBuffer(long l) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: create buffer of size {1}B and default memory access", new Object[]{this, l});
			return new LoggingBuffer(delegate.createBuffer(l), flags);
		}

		@Override
		public Buffer createBufferFromHost(ByteBuffer bb, MemoryAccess ma) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: create buffer from host {1} and memory access {2}", new Object[]{this, bb, ma});
			return new LoggingBuffer(delegate.createBufferFromHost(bb, ma), flags);
		}

		@Override
		public Buffer createBufferFromHost(ByteBuffer bb) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: create buffer from host {1} and default memory access", new Object[]{this, bb});
			return new LoggingBuffer(delegate.createBufferFromHost(bb), flags);
		}

		@Override
		public Image createImage(MemoryAccess ma, Image.ImageFormat i, Image.ImageDescriptor id) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: create image with format {1}, descriptor {2} and memory access {3}", new Object[]{this, i, id, ma});
			return new LoggingImage(delegate.createImage(ma, i, id), flags);
		}

		@Override
		public Image.ImageFormat[] querySupportedFormats(MemoryAccess ma, Image.ImageType it) {
			return delegate.querySupportedFormats(ma, it);
		}

		@Override
		public Buffer bindVertexBuffer(VertexBuffer vb, MemoryAccess ma) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: bind vertex buffer {1} with memory access {2}", new Object[]{this, vb, ma});
			return new LoggingBuffer(delegate.bindVertexBuffer(vb, ma), flags);
		}

		@Override
		public Image bindImage(com.jme3.texture.Image image, Texture.Type type, int i, MemoryAccess ma) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: bind image {1} with texture type {2} mipmap level {3} and mem access {4}", new Object[]{this, image, type, i, ma});
			return new LoggingImage(delegate.bindImage(image, type, i, ma), flags);
		}

		@Override
		public Image bindImage(Texture txtr, int i, MemoryAccess ma) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: bind texture {1}, mipmap level {2}, memory access {3}", new Object[]{this, txtr, i, ma});
			return new LoggingImage(delegate.bindImage(txtr, i, ma), flags);
		}

		@Override
		public Image bindImage(Texture txtr, MemoryAccess ma) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: bind texture {1} with memory access {2}", new Object[]{this, txtr, ma});
			return new LoggingImage(delegate.bindImage(txtr, ma), flags);
		}

		@Override
		public Image bindRenderBuffer(FrameBuffer.RenderBuffer rb, MemoryAccess ma) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: bind render buffer {1} with  memory access {2}", new Object[]{this, rb, ma});
			return new LoggingImage(delegate.bindRenderBuffer(rb, ma), flags);
		}

		@Override
		public Program createProgramFromSourceCode(String string) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: create program with source code\n{1}", new Object[]{this, string});
			return new LoggingProgram(delegate.createProgramFromSourceCode(string), flags);
		}

		@Override
		public Program createProgramFromSourceCodeWithDependencies(String string, AssetManager am) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: create program with source code and dependencies\n{1}", new Object[]{this, string});
			return super.createProgramFromSourceCodeWithDependencies(string, am);
		}

		@Override
		public Program createProgramFromSourceFilesWithInclude(AssetManager am, String string, List<String> list) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "{0}: create program with source files {2} and include \"{1}\"", new Object[]{this, string, list});
			return super.createProgramFromSourceFilesWithInclude(am, string, list);
		}

		@Override
		public Program createProgramFromBinary(ByteBuffer bb, Device device) {
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.info(this+": create program from binary "+bb+" for device "+device);
			return new LoggingProgram(delegate.createProgramFromBinary(bb, device), flags);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}

		@Override
		protected Image bindPureRenderBuffer(FrameBuffer.RenderBuffer rb, MemoryAccess ma) {
			throw new UnsupportedOperationException("Not supported yet.");
		}
		
	}
	
	private static class LoggingCommandQueue extends CommandQueue {
		private final CommandQueue delegate;
		private final int flags;

		public LoggingCommandQueue(CommandQueue delegate, int flags) {
			super(new LoggingReleaser(((flags & FLAG_LOG_DELETION) != 0) ? delegate.toString() : null, delegate.getReleaser()), null);
			this.delegate = delegate;
			this.flags = flags;
		}

		@Override
		public CommandQueue register() {
			delegate.register();
			if ((flags & FLAG_LOG_REGISTER)!=0) LOG.log(Level.INFO, "{0} registered", this);
			return this;
		}

		@Override
		public Device getDevice() {
			return delegate.getDevice();
		}

		@Override
		public void flush() {
			if ((flags & FLAG_LOG_FINISH) != 0) LOG.log(Level.INFO, "{0}: flush()", this);
			delegate.flush();
		}

		@Override
		public void finish() {
			if ((flags & FLAG_LOG_FINISH) != 0) LOG.log(Level.INFO, "{0}: finish()", this);
			delegate.finish();
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}
		
	}
	
	private static class LoggingEvent extends Event {
		private final Event delegate;
		private final int flags;

		public LoggingEvent(Event delegate, int flags) {
			super(new LoggingReleaser(((flags & FLAG_LOG_DELETION) != 0) ? delegate.toString() : null, delegate.getReleaser()));
			this.delegate = delegate;
			this.flags = flags;
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.info("event created");
		}

		@Override
		public Event register() {
			delegate.register();
			if ((flags & FLAG_LOG_REGISTER)!=0) LOG.log(Level.INFO, "{0} registered", this);
			return this;
		}

		@Override
		public void waitForFinished() {
			if ((flags & FLAG_LOG_EVENT) != 0) LOG.log(Level.INFO, "{0}: waitForFinished()", this);
			delegate.waitForFinished();
			if ((flags & FLAG_LOG_EVENT) != 0) LOG.log(Level.INFO, "{0}: waiting done", this);
		}

		@Override
		public boolean isCompleted() {
			boolean b = delegate.isCompleted();
			if ((flags & FLAG_LOG_EVENT) != 0) LOG.log(Level.INFO, "{0}: isCompleted={1}", new Object[]{this, b});
			return b;
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}
		
	}
	
	private static class LoggingBuffer extends Buffer {
		private final Buffer delegate;
		private final int flags;

		public LoggingBuffer(Buffer delegate, int flags) {
			super(new LoggingReleaser(((flags & FLAG_LOG_DELETION) != 0) ? delegate.toString() : null, delegate.getReleaser()));
			this.delegate = delegate;
			this.flags = flags;
		}

		@Override
		public Buffer register() {
			delegate.register();
			if ((flags & FLAG_LOG_REGISTER)!=0) LOG.log(Level.INFO, "{0} registered", this);
			return this;
		}

		@Override
		public long getSize() {
			return delegate.getSize();
		}

		@Override
		public MemoryAccess getMemoryAccessFlags() {
			return delegate.getMemoryAccessFlags();
		}

		@Override
		public void read(CommandQueue cq, ByteBuffer bb, long l, long l1) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: read from {1}, size={2}, offset={3}", new Object[]{this, bb, l, l1});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.read(((LoggingCommandQueue) cq).delegate, bb, l, l1);
		}

		@Override
		public void read(CommandQueue cq, ByteBuffer bb, long l) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: read from {1}, size={2}", new Object[]{this, bb, l});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.read(((LoggingCommandQueue) cq).delegate, bb, l);
		}

		@Override
		public void read(CommandQueue cq, ByteBuffer bb) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: read from {1}", new Object[]{this, bb});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.read(((LoggingCommandQueue) cq).delegate, bb);
		}

		@Override
		public Event readAsync(CommandQueue cq, ByteBuffer bb, long l, long l1) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async read from {1}, size={2}, offset={3}", new Object[]{this, bb, l, l1});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.readAsync(((LoggingCommandQueue) cq).delegate, bb, l, l1), flags);
		}

		@Override
		public Event readAsync(CommandQueue cq, ByteBuffer bb, long l) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async read from {1}, size={2}", new Object[]{this, bb, l});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.readAsync(((LoggingCommandQueue) cq).delegate, bb, l), flags);
		}

		@Override
		public Event readAsync(CommandQueue cq, ByteBuffer bb) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async read from {1}", new Object[]{this, bb});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.readAsync(((LoggingCommandQueue) cq).delegate, bb), flags);
		}

		@Override
		public void write(CommandQueue cq, ByteBuffer bb, long l, long l1) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: write to {1}, size={2}, offset={3}", new Object[]{this, bb, l, l1});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.write(((LoggingCommandQueue) cq).delegate, bb, l, l1);
		}

		@Override
		public void write(CommandQueue cq, ByteBuffer bb, long l) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: write to {1}, size={2}", new Object[]{this, bb, l});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.write(((LoggingCommandQueue) cq).delegate, bb, l);
		}

		@Override
		public void write(CommandQueue cq, ByteBuffer bb) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: write to {1}", new Object[]{this, bb});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.write(((LoggingCommandQueue) cq).delegate, bb);
		}

		@Override
		public Event writeAsync(CommandQueue cq, ByteBuffer bb, long l, long l1) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async write to {1}, size={2}, offset={3}", new Object[]{this, bb, l, l1});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.writeAsync(((LoggingCommandQueue) cq).delegate, bb, l, l1), flags);
		}

		@Override
		public Event writeAsync(CommandQueue cq, ByteBuffer bb, long l) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async write to {1}, size={2}", new Object[]{this, bb, l});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.writeAsync(((LoggingCommandQueue) cq).delegate, bb, l), flags);
		}

		@Override
		public Event writeAsync(CommandQueue cq, ByteBuffer bb) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async write to {1}", new Object[]{this, bb});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.writeAsync(((LoggingCommandQueue) cq).delegate, bb), flags);
		}

		@Override
		public void copyTo(CommandQueue cq, Buffer buffer, long l, long l1, long l2) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "copy {0} to {1}, size={2}, src offset={3}, dest offset={4}", new Object[]{this, buffer, l, l1, l2});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof LoggingBuffer)) throwWrongType(buffer);
			delegate.copyTo(((LoggingCommandQueue) cq).delegate, ((LoggingBuffer) buffer).delegate, l, l1, l2);
		}

		@Override
		public void copyTo(CommandQueue cq, Buffer buffer, long l) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "copy {0} to {1}, size={2}", new Object[]{this, buffer, l});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof LoggingBuffer)) throwWrongType(buffer);
			delegate.copyTo(((LoggingCommandQueue) cq).delegate, ((LoggingBuffer) buffer).delegate, l);
		}

		@Override
		public void copyTo(CommandQueue cq, Buffer buffer) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "copy {0} to {1}", new Object[]{this, buffer});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof LoggingBuffer)) throwWrongType(buffer);
			delegate.copyTo(((LoggingCommandQueue) cq).delegate, ((LoggingBuffer) buffer).delegate);
		}

		@Override
		public Event copyToAsync(CommandQueue cq, Buffer buffer, long l, long l1, long l2) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "async copy {0} to {1}, size={2}, src offset={3}, dest offset={4}", new Object[]{this, buffer, l, l1, l2});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof LoggingBuffer)) throwWrongType(buffer);
			return new LoggingEvent(delegate.copyToAsync(((LoggingCommandQueue) cq).delegate, ((LoggingBuffer) buffer).delegate, l, l1, l2), flags);
		}

		@Override
		public Event copyToAsync(CommandQueue cq, Buffer buffer, long l) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "async copy {0} to {1}, size={2}", new Object[]{this, buffer, l});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof LoggingBuffer)) throwWrongType(buffer);
			return new LoggingEvent(delegate.copyToAsync(((LoggingCommandQueue) cq).delegate, ((LoggingBuffer) buffer).delegate, l), flags);
		}

		@Override
		public Event copyToAsync(CommandQueue cq, Buffer buffer) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "async copy {0} to {1}", new Object[]{this, buffer});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof LoggingBuffer)) throwWrongType(buffer);
			return new LoggingEvent(delegate.copyToAsync(((LoggingCommandQueue) cq).delegate, ((LoggingBuffer) buffer).delegate), flags);
		}

		@Override
		public ByteBuffer map(CommandQueue cq, long l, long l1, MappingAccess ma) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: map with access {1}, size={2}, offset={3}", new Object[]{this, ma, l, l1});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return delegate.map(((LoggingCommandQueue) cq).delegate, l, l1, ma);
		}

		@Override
		public ByteBuffer map(CommandQueue cq, long l, MappingAccess ma) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: map with access {1}, size={2}", new Object[]{this, ma, l});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return delegate.map(((LoggingCommandQueue) cq).delegate, l, ma);
		}

		@Override
		public ByteBuffer map(CommandQueue cq, MappingAccess ma) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: map with access {1}", new Object[]{this, ma});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return delegate.map(((LoggingCommandQueue) cq).delegate, ma);
		}

		@Override
		public void unmap(CommandQueue cq, ByteBuffer bb) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: unmap {1}", new Object[]{this, bb});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.unmap(((LoggingCommandQueue) cq).delegate, bb);
		}

		@Override
		public AsyncMapping mapAsync(CommandQueue cq, long l, long l1, MappingAccess ma) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async map with access {1}, size={2}, offset={3}", new Object[]{this, ma, l, l1});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return delegate.mapAsync(((LoggingCommandQueue) cq).delegate, l, l1, ma);
		}

		@Override
		public AsyncMapping mapAsync(CommandQueue cq, long l, MappingAccess ma) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async map with access {1}, size={2}", new Object[]{this, ma, l});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return delegate.mapAsync(((LoggingCommandQueue) cq).delegate, l, ma);
		}

		@Override
		public AsyncMapping mapAsync(CommandQueue cq, MappingAccess ma) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async map with access {1}", new Object[]{this, ma});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return delegate.mapAsync(((LoggingCommandQueue) cq).delegate, ma);
		}

		@Override
		public Event fillAsync(CommandQueue cq, ByteBuffer bb, long l, long l1) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.info(this+": fill with pattern "+bb+", size="+l+", offset="+l1);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.fillAsync(((LoggingCommandQueue) cq).delegate, bb, l, l1), flags);
		}

		@Override
		public Event copyToImageAsync(CommandQueue cq, Image image, long l, long[] longs, long[] longs1) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "copy {0} to {1}, src offset={2}, dest origin={3}, dest region={4}", new Object[]{this, image, l, Arrays.toString(longs), Arrays.toString(longs1)});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			if (!(image instanceof LoggingImage)) throwWrongType(image);
			return new LoggingEvent(delegate.copyToImageAsync(((LoggingCommandQueue) cq).delegate, ((LoggingImage) image).delegate, l, longs, longs1), flags);
		}

		@Override
		public Event acquireBufferForSharingAsync(CommandQueue cq) {
			if ((flags & FLAG_LOG_ACQUIRE) != 0) LOG.log(Level.INFO, "aquire {0}", this);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.acquireBufferForSharingAsync(((LoggingCommandQueue) cq).delegate), flags);
		}

		@Override
		public void acquireBufferForSharingNoEvent(CommandQueue cq) {
			if ((flags & FLAG_LOG_ACQUIRE) != 0) LOG.log(Level.INFO, "aquire {0}", this);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.acquireBufferForSharingNoEvent(((LoggingCommandQueue) cq).delegate);
		}

		@Override
		public Event releaseBufferForSharingAsync(CommandQueue cq) {
			if ((flags & FLAG_LOG_ACQUIRE) != 0) LOG.log(Level.INFO, "release {0}", this);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.releaseBufferForSharingAsync(((LoggingCommandQueue) cq).delegate), flags);
		}

		@Override
		public void releaseBufferForSharingNoEvent(CommandQueue cq) {
			if ((flags & FLAG_LOG_ACQUIRE) != 0) LOG.log(Level.INFO, "release {0}", this);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.releaseBufferForSharingNoEvent(((LoggingCommandQueue) cq).delegate);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}
		
		
	}
	
	private static class LoggingImage extends Image {
		private final Image delegate;
		private final int flags;

		public LoggingImage(Image delegate, int flags) {
			super(new LoggingReleaser(((flags & FLAG_LOG_DELETION) != 0) ? delegate.toString() : null, delegate.getReleaser()));
			this.delegate = delegate;
			this.flags = flags;
		}

		@Override
		public Image register() {
			delegate.register();
			if ((flags & FLAG_LOG_REGISTER)!=0) LOG.log(Level.INFO, "{0} registered", this);
			return this;
		}

		@Override
		public long getWidth() {
			return delegate.getWidth();
		}

		@Override
		public long getHeight() {
			return delegate.getHeight();
		}

		@Override
		public long getDepth() {
			return delegate.getDepth();
		}

		@Override
		public long getRowPitch() {
			return delegate.getRowPitch();
		}

		@Override
		public long getSlicePitch() {
			return delegate.getSlicePitch();
		}

		@Override
		public long getArraySize() {
			return delegate.getArraySize();
		}

		@Override
		public ImageFormat getImageFormat() {
			return delegate.getImageFormat();
		}

		@Override
		public ImageType getImageType() {
			return delegate.getImageType();
		}

		@Override
		public int getElementSize() {
			return delegate.getElementSize();
		}

		@Override
		public void readImage(CommandQueue cq, ByteBuffer bb, long[] longs, long[] longs1, long l, long l1) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: read from {1}, origin={2}, region={3}, row offset={4}, slice offset={5}", new Object[]{this, bb, Arrays.toString(longs), Arrays.toString(longs1), l, l1});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.readImage(((LoggingCommandQueue) cq).delegate, bb, longs, longs1, l, l1);
		}

		@Override
		public Event readImageAsync(CommandQueue cq, ByteBuffer bb, long[] longs, long[] longs1, long l, long l1) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async read from {1}, origin={2}, region={3}, row offset={4}, slice offset={5}", new Object[]{this, bb, Arrays.toString(longs), Arrays.toString(longs1), l, l1});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.readImageAsync(((LoggingCommandQueue) cq).delegate, bb, longs, longs1, l, l1), flags);
		}

		@Override
		public void writeImage(CommandQueue cq, ByteBuffer bb, long[] longs, long[] longs1, long l, long l1) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: write to {1}, origin={2}, region={3}, row offset={4}, slice offset={5}", new Object[]{this, bb, Arrays.toString(longs), Arrays.toString(longs1), l, l1});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.writeImage(((LoggingCommandQueue) cq).delegate, bb, longs, longs1, l, l1);
		}

		@Override
		public Event writeImageAsync(CommandQueue cq, ByteBuffer bb, long[] longs, long[] longs1, long l, long l1) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async write to {1}, origin={2}, region={3}, row offset={4}, slice offset={5}", new Object[]{this, bb, Arrays.toString(longs), Arrays.toString(longs1), l, l1});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.writeImageAsync(((LoggingCommandQueue) cq).delegate, bb, longs, longs1, l, l1), flags);
		}

		@Override
		public void copyTo(CommandQueue cq, Image image, long[] longs, long[] longs1, long[] longs2) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "copy {0} to {1}, src origin={2}, dest origin={3}, region={4}", new Object[]{this, image, Arrays.toString(longs), Arrays.toString(longs1), Arrays.toString(longs2)});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			if (!(image instanceof LoggingImage)) throwWrongType(image);
			delegate.copyTo(((LoggingCommandQueue) cq).delegate, ((LoggingImage) image).delegate, longs, longs1, longs2);
		}

		@Override
		public Event copyToAsync(CommandQueue cq, Image image, long[] longs, long[] longs1, long[] longs2) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "async copy {0} to {1}, src origin={2}, dest origin={3}, region={4}", new Object[]{this, image, Arrays.toString(longs), Arrays.toString(longs1), Arrays.toString(longs2)});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			if (!(image instanceof LoggingImage)) throwWrongType(image);
			return new LoggingEvent(delegate.copyToAsync(((LoggingCommandQueue) cq).delegate, ((LoggingImage) image).delegate, longs, longs1, longs2), flags);
		}

		@Override
		public ImageMapping map(CommandQueue cq, long[] longs, long[] longs1, MappingAccess ma) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: map with memory access {1}, origin={2}, region={3}", new Object[]{this, ma, Arrays.toString(longs), Arrays.toString(longs1)});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return delegate.map(((LoggingCommandQueue) cq).delegate, longs, longs1, ma);
		}

		@Override
		public ImageMapping mapAsync(CommandQueue cq, long[] longs, long[] longs1, MappingAccess ma) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: async map with memory access {1}, origin={2}, region={3}", new Object[]{this, ma, Arrays.toString(longs), Arrays.toString(longs1)});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return delegate.mapAsync(((LoggingCommandQueue) cq).delegate, longs, longs1, ma);
		}

		@Override
		public void unmap(CommandQueue cq, ImageMapping im) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: unmap {1}", new Object[]{this, im.buffer});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.unmap(((LoggingCommandQueue) cq).delegate, im);
		}

		@Override
		public Event fillAsync(CommandQueue cq, long[] longs, long[] longs1, ColorRGBA crgba) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: fill with {1}, origin={2}, region={3}", new Object[]{this, crgba, Arrays.toString(longs), Arrays.toString(longs1)});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.fillAsync(((LoggingCommandQueue) cq).delegate, longs, longs1, crgba), flags);
		}

		@Override
		public Event fillAsync(CommandQueue cq, long[] longs, long[] longs1, int[] ints) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "{0}: fill with {1}, origin={2}, region={3}", new Object[]{this, Arrays.toString(ints), Arrays.toString(longs), Arrays.toString(longs1)});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.fillAsync(((LoggingCommandQueue) cq).delegate, longs, longs1, ints), flags);
		}

		@Override
		public Event copyToBufferAsync(CommandQueue cq, Buffer buffer, long[] longs, long[] longs1, long l) {
			if ((flags & FLAG_LOG_MEMOP) != 0) LOG.log(Level.INFO, "async copy {0} to {1}, src origin={2}, region={4}, dest offset={3}", new Object[]{this, buffer, Arrays.toString(longs), l, Arrays.toString(longs1)});
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			if (!(buffer instanceof LoggingBuffer)) throwWrongType(buffer);
			return new LoggingEvent(delegate.copyToBufferAsync(((LoggingCommandQueue) cq).delegate, ((LoggingBuffer) buffer).delegate, longs, longs1, l), flags);
		}

		@Override
		public Event acquireImageForSharingAsync(CommandQueue cq) {
			if ((flags & FLAG_LOG_ACQUIRE) != 0) LOG.log(Level.INFO, "aquire {0}", this);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.acquireImageForSharingAsync(((LoggingCommandQueue) cq).delegate), flags);
		}

		@Override
		public void acquireImageForSharingNoEvent(CommandQueue cq) {
			if ((flags & FLAG_LOG_ACQUIRE) != 0) LOG.log(Level.INFO, "aquire {0}", this);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.acquireImageForSharingNoEvent(((LoggingCommandQueue) cq).delegate);
		}

		@Override
		public Event releaseImageForSharingAsync(CommandQueue cq) {
			if ((flags & FLAG_LOG_ACQUIRE) != 0) LOG.log(Level.INFO, "release {0}", this);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.releaseImageForSharingAsync(((LoggingCommandQueue) cq).delegate), flags);
		}

		@Override
		public void releaseImageForSharingNoEvent(CommandQueue cq) {
			if ((flags & FLAG_LOG_ACQUIRE) != 0) LOG.log(Level.INFO, "release {0}", this);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.releaseImageForSharingNoEvent(((LoggingCommandQueue) cq).delegate);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}
			
	}
	
	private static class LoggingProgram extends Program {
		private final Program delegate;
		private final int flags;

		public LoggingProgram(Program delegate, int flags) {
			super(new LoggingReleaser(((flags & FLAG_LOG_DELETION) != 0) ? delegate.toString() : null, delegate.getReleaser()));
			this.delegate = delegate;
			this.flags = flags;
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.log(Level.INFO, "program {0} created", this);
		}

		@Override
		public Program register() {
			delegate.register();
			if ((flags & FLAG_LOG_REGISTER)!=0) LOG.log(Level.INFO, "{0} registered", this);
			return this;
		}

		@Override
		public void build(String string, Device... devices) throws KernelCompilationException {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: build with args {1} on the devices {2}", new Object[]{this, string, Arrays.toString(devices)});
			delegate.build(string, devices);
		}

		@Override
		public void build() throws KernelCompilationException {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: build", this);
			delegate.build();
		}

		@Override
		public Kernel createKernel(String string) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: create kernel with the name {1}", new Object[]{this, string});
			return new LoggingKernel(delegate.createKernel(string), flags);
		}

		@Override
		public Kernel[] createAllKernels() {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: create all kernels", this);
			Kernel[] kx = delegate.createAllKernels();
			Kernel[] dkx = new Kernel[kx.length];
			for (int i=0; i<kx.length; ++i) {
				dkx[i] = new LoggingKernel(kx[i], flags);
			}
			return dkx;
		}

		@Override
		public ByteBuffer getBinary(Device device) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: get binary for device {1}", new Object[]{this, device});
			return delegate.getBinary(device);
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}
		
	}
	
	private static class LoggingKernel extends Kernel {
		private final Kernel delegate;
		private final int flags;

		public LoggingKernel(Kernel delegate, int flags) {
			super(new LoggingReleaser(((flags & FLAG_LOG_DELETION) != 0) ? delegate.toString() : null, delegate.getReleaser()));
			this.delegate = delegate;
			this.flags = flags;
			if ((flags & FLAG_LOG_CREATION) != 0) LOG.info("kernel "+this+" created");
		}

		@Override
		public Kernel register() {
			delegate.register();
			if ((flags & FLAG_LOG_REGISTER)!=0) LOG.log(Level.INFO, "{0} registered", this);
			return this;
		}

		@Override
		public String getName() {
			return delegate.getName();
		}

		@Override
		public int getArgCount() {
			return delegate.getArgCount();
		}

		@Override
		public WorkSize getGlobalWorkSize() {
			return delegate.getGlobalWorkSize();
		}

		@Override
		public void setGlobalWorkSize(WorkSize ws) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set global work size to {1}", new Object[]{this, ws});
			delegate.setGlobalWorkSize(ws);
		}

		@Override
		public void setGlobalWorkSize(int i) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set global work size to {1}", new Object[]{this, i});
			delegate.setGlobalWorkSize(i);
		}

		@Override
		public void setGlobalWorkSize(int i, int i1) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set global work size to ({1}, {2})", new Object[]{this, i, i1});
			delegate.setGlobalWorkSize(i, i1);
		}

		@Override
		public void setGlobalWorkSize(int i, int i1, int i2) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set global work size to ({1}, {2}, {3})", new Object[]{this, i, i1, i2});
			delegate.setGlobalWorkSize(i, i1, i2);
		}

		@Override
		public WorkSize getWorkGroupSize() {
			return delegate.getWorkGroupSize();
		}

		@Override
		public void setWorkGroupSize(WorkSize ws) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set work group size to {1}", new Object[]{this, ws});
			delegate.setWorkGroupSize(ws);
		}

		@Override
		public void setWorkGroupSize(int i) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set work group size to {1}", new Object[]{this, i});
			delegate.setWorkGroupSize(i);
		}

		@Override
		public void setWorkGroupSize(int i, int i1) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set work group size to ({1}, {2})", new Object[]{this, i, i1});
			delegate.setWorkGroupSize(i, i1);
		}

		@Override
		public void setWorkGroupSdize(int i, int i1, int i2) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set work group size to ({1}, {2}, {3})", new Object[]{this, i, i1, i2});
			delegate.setWorkGroupSdize(i, i1, i2);
		}

		@Override
		public void setWorkGroupSizeToNull() {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set work group size to null", this);
			delegate.setWorkGroupSizeToNull();
		}

		@Override
		public long getMaxWorkGroupSize(Device device) {
			return delegate.getMaxWorkGroupSize(device);
		}

		@Override
		public void setArg(int i, LocalMemPerElement lmpe) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to {2}", new Object[]{this, i, lmpe});
			delegate.setArg(i, lmpe);
		}

		@Override
		public void setArg(int i, LocalMem lm) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to {2}", new Object[]{this, i, lm});
			delegate.setArg(i, lm);
		}

		@Override
		public void setArg(int i, Buffer buffer) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to {2}", new Object[]{this, i, buffer});
			if (!(buffer instanceof LoggingBuffer)) throwWrongType(buffer);
			delegate.setArg(i, ((LoggingBuffer) buffer).delegate);
		}

		@Override
		public void setArg(int i, Image image) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to {2}", new Object[]{this, i, image});
			if (!(image instanceof LoggingImage)) throwWrongType(image);
			delegate.setArg(i, ((LoggingImage) image).delegate);
		}

		@Override
		public void setArg(int i, byte b) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to (byte) {2}", new Object[]{this, i, b});
			delegate.setArg(i, b);
		}

		@Override
		public void setArg(int i, short s) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to (short) {2}", new Object[]{this, i, s});
			delegate.setArg(i, s);
		}

		@Override
		public void setArg(int i, int i1) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to (int) {2}", new Object[]{this, i, i1});
			delegate.setArg(i, i1);
		}

		@Override
		public void setArg(int i, long l) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to (long) {2}", new Object[]{this, i, l});
			delegate.setArg(i, l);
		}

		@Override
		public void setArg(int i, float f) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to (float) {2}", new Object[]{this, i, f});
			delegate.setArg(i, f);
		}

		@Override
		public void setArg(int i, double d) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to (double) {2}", new Object[]{this, i, d});
			delegate.setArg(i, d);
		}

		@Override
		public void setArg(int i, Vector2f vctrf) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to {2}", new Object[]{this, i, vctrf});
			delegate.setArg(i, vctrf);
		}

		@Override
		public void setArg(int i, Vector4f vctrf) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to {2}", new Object[]{this, i, vctrf});
			delegate.setArg(i, vctrf);
		}

		@Override
		public void setArg(int i, Quaternion qtrn) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to {2}", new Object[]{this, i, qtrn});
			delegate.setArg(i, qtrn);
		}

		@Override
		public void setArg(int i, Matrix4f mtrxf) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to {2}", new Object[]{this, i, mtrxf});
			delegate.setArg(i, mtrxf);
		}

		@Override
		public void setArg(int i, Matrix3f mtrxf) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set arg {1} to {2}", new Object[]{this, i, mtrxf});
			delegate.setArg(i, mtrxf);
		}

		@Override
		public void setArg(int i, ByteBuffer bb, long l) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: set raw arg {1} to {2} with size ", new Object[]{this, i, bb, l});
			delegate.setArg(i, bb, l);
		}

		@Override
		public Event Run(CommandQueue cq) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: direct call", this);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.Run(((LoggingCommandQueue) cq).delegate), flags);
		}

		@Override
		public void RunNoEvent(CommandQueue cq) {
			if ((flags & FLAG_LOG_KERNEL) != 0) LOG.log(Level.INFO, "{0}: direct call", this);
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.RunNoEvent(((LoggingCommandQueue) cq).delegate);
		}
		
		private Object[] unwrap(Object[] os) {
			Object[] os2 = new Object[os.length];
			for (int i=0; i<os.length; ++i) {
				if (os[i] instanceof Buffer) {
					if (!(os[i] instanceof LoggingBuffer)) throwWrongType(os[i]);
					os2[i] = ((LoggingBuffer) os[i]).delegate;
				} else if (os[i] instanceof Image) {
					if (!(os[i] instanceof LoggingImage)) throwWrongType(os[i]);
					os2[i] = ((LoggingImage) os[i]).delegate;
				} else {
					os2[i] = os[i];
				}
			}
			return os2;
		}

		private Object[] unwrap(Object[] os, StringBuilder args) {
			Object[] os2 = new Object[os.length];
			for (int i=0; i<os.length; ++i) {
				if (i>0) {
					args.append(", ");
				}
				if (os[i] instanceof Buffer) {
					if (!(os[i] instanceof LoggingBuffer)) throwWrongType(os[i]);
					os2[i] = ((LoggingBuffer) os[i]).delegate;
					args.append(os2[i]);
				} else if (os[i] instanceof Image) {
					if (!(os[i] instanceof LoggingImage)) throwWrongType(os[i]);
					os2[i] = ((LoggingImage) os[i]).delegate;
					args.append(os2[i]);
				} else if (os[i] instanceof Byte) {
					os2[i] = os[i];
					args.append("(byte) ").append(os[i]);
				} else if (os[i] instanceof Short) {
					os2[i] = os[i];
					args.append("(short) ").append(os[i]);
				} else if (os[i] instanceof Integer) {
					os2[i] = os[i];
					args.append("(int) ").append(os[i]);
				} else if (os[i] instanceof Long) {
					os2[i] = os[i];
					args.append("(long) ").append(os[i]);
				} else if (os[i] instanceof Float) {
					os2[i] = os[i];
					args.append("(float) ").append(os[i]);
				} else if (os[i] instanceof Double) {
					os2[i] = os[i];
					args.append("(double) ").append(os[i]);
				} else {
					os2[i] = os[i];
				}
			}
			return os2;
		}
		
		@Override
		public Event Run1(CommandQueue cq, WorkSize ws, Object... os) {
			if ((flags & FLAG_LOG_KERNEL) != 0) {
				StringBuilder s = new StringBuilder();
				s.append(getName()).append('(');
				os = unwrap(os, s);
				s.append(")  global work size=").append(ws);
				LOG.info(s.toString());
			} else {
				os = unwrap(os);
			}
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.Run1(((LoggingCommandQueue) cq).delegate, ws, os), flags);
		}

		@Override
		public void Run1NoEvent(CommandQueue cq, WorkSize ws, Object... os) {
			if ((flags & FLAG_LOG_KERNEL) != 0) {
				StringBuilder s = new StringBuilder();
				s.append(getName()).append('(');
				os = unwrap(os, s);
				s.append(")  global work size=").append(ws);
				LOG.info(s.toString());
			} else {
				os = unwrap(os);
			}
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.Run1NoEvent(((LoggingCommandQueue) cq).delegate, ws, os);
		}

		@Override
		public Event Run2(CommandQueue cq, WorkSize ws, WorkSize ws1, Object... os) {
			if ((flags & FLAG_LOG_KERNEL) != 0) {
				StringBuilder s = new StringBuilder();
				s.append(getName()).append('(');
				os = unwrap(os, s);
				s.append(")  global work size=").append(ws);
				s.append("  local work group size=").append(ws1);
				LOG.info(s.toString());
			} else {
				os = unwrap(os);
			}
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			return new LoggingEvent(delegate.Run2(((LoggingCommandQueue) cq).delegate, ws, ws1, os), flags);
		}

		@Override
		public void Run2NoEvent(CommandQueue cq, WorkSize ws, WorkSize ws1, Object... os) {
			if ((flags & FLAG_LOG_KERNEL) != 0) {
				StringBuilder s = new StringBuilder();
				s.append(getName()).append('(');
				os = unwrap(os, s);
				s.append(")  global work size=").append(ws);
				s.append("  local work group size=").append(ws1);
				LOG.info(s.toString());
			} else {
				os = unwrap(os);
			}
			if (!(cq instanceof LoggingCommandQueue)) throwWrongType(cq);
			delegate.Run2NoEvent(((LoggingCommandQueue) cq).delegate, ws, ws1, os);
		}

		@Override
		public String toString() {
			return delegate.toString();
		}

		@Override
		public int hashCode() {
			return delegate.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return delegate.equals(o);
		}
		
	}
} public class SharedTexture {
	
	private Texture texture;
	private Image image;
	private com.jme3.opencl.Image clImage;
	private com.jme3.opencl.Image.ImageDescriptor descriptor;
	private Image.Format format;
	private GLRenderer renderer;

	/**
	 * Creates a new shared texture with the specified dimensions, type and format
	 * @param descriptor specifies dimensions and type
	 * @param format specifies the format
	 */
	public SharedTexture(com.jme3.opencl.Image.ImageDescriptor descriptor, Image.Format format) {
		if (descriptor == null) {
			throw new NullPointerException("descriptor is null");
		}
		if (format == null) {
			throw new NullPointerException("format is null");
		}
		this.descriptor = descriptor;
		this.format = format;
	}
	
	/**
	 * Creates a new shared texture from an already existing jME texture.
	 * Only the creation of the shared OpenCL image is left
	 * @param texture 
	 */
	public SharedTexture(Texture texture) {
		this.texture = texture;
		this.image = texture.getImage();
	}

	/**
	 * Checks if the image is already initialized.
	 * @return {@code true} if already initialized
	 */
	public boolean isInitialized() {
		return clImage != null;
	}
	
	/**
	 * Initializes the shared textures.
	 * The texture is created (not with the constructor taking the texture as argument),
	 * uploaded to the GPU and shared with OpenCL.
	 * Must be called from the jME main thread.
	 * 
	 * @param renderManager the render manager
	 * @param clContext the OpenCL context
	 * @param memoryAccess the allowed memory access to the OpenCL texture
	 * 
	 * @throws IllegalStateException if it was already initialized
	 * @throws IllegalArgumentException 
	 *   If the underlying renderer implementation is not {@code GLRenderer}, or
	 *   if the OpenCL texture type can't be mapped to a texture type of jME
	 */
	public void initialize(RenderManager renderManager, Context clContext, MemoryAccess memoryAccess) {
		if (isInitialized()) {
			throw new IllegalStateException("already initialized");
		}
		//get renderer
		if (renderManager.getRenderer() instanceof GLRenderer) {
			renderer = (GLRenderer) renderManager.getRenderer();
		} else {
			throw new IllegalArgumentException("Only GLRenderer supported");
		}
		
		//create texture
		if (texture == null) {
			switch (descriptor.type) {
				case IMAGE_2D:
					texture = new Texture2D((int) descriptor.width, (int) descriptor.height, format);
					break;
				case IMAGE_2D_ARRAY:
					ArrayList<Image> images = new ArrayList<>((int) descriptor.arraySize);
					for (int i=0; i<descriptor.arraySize; ++i) {
						images.add(new Image(format, (int) descriptor.width, (int) descriptor.height, null, ColorSpace.Linear));
					}
					texture = new TextureArray(images);
					break;
				case IMAGE_3D:
					texture = new Texture3D((int) descriptor.width, (int) descriptor.height, (int) descriptor.depth, format);
					break;
				default:
					throw new IllegalArgumentException("unsupported texture type: "+descriptor.type);
			}
			image = texture.getImage();
		}
		
		//upload texture to GPU
		renderer.updateTexImageData(image, texture.getType(), 0, false);
		
		//create shared image
		clImage = clContext.bindImage(texture, memoryAccess).register();
	}
	
	/**
	 * Returns the jME texture.
	 * @return 
	 */
	public Texture getJMETexture() {
		return texture;
	}
	
	/**
	 * Returns the OpenCL image
	 * @return 
	 */
	public com.jme3.opencl.Image getCLImage() {
		return clImage;
	}
} public class TransferFunctionEditor implements Savable, Serializable {

	private static final Logger LOG = Logger.getLogger(TransferFunctionEditor.class.getName());
	private static final int RESOLUTION = 256;
	private static final int[] LINE_WIDTHS = {2,3};
	private static final int[] POINT_SIZES = {4,5,8};
	private static final String CHANNEL_NAMES[] = {"Red", "Green", "Blue", "Alpha"};
	private static final ColorRGBA CHANNEL_COLORS[] = {
		new ColorRGBA(1, 0, 0, 1),
		new ColorRGBA(0, 1, 0, 1),
		new ColorRGBA(0, 0, 1, 1),
		new ColorRGBA(0.5f, 0.5f, 1, 1)
	};
	private static final float Y_SIZE = 0.8f;

	private static final String KEY_PREFIX = "TFE_";
	private static final String MOUSE_MOVE_Xn = KEY_PREFIX + "x-";
	private static final String MOUSE_MOVE_Xp = KEY_PREFIX + "x+";
	private static final String MOUSE_MOVE_Yn = KEY_PREFIX + "y-";
	private static final String MOUSE_MOVE_Yp = KEY_PREFIX + "y+";
	private static final String MOUSE_CLICK_LEFT = KEY_PREFIX + "clickL";
	private static final String MOUSE_CLICK_RIGHT = KEY_PREFIX + "clickR";
	
	private transient boolean enabled;
	private transient Application app;
	private transient Listener listener;
	private transient Node node;
	private transient Texture[] textures;
	
	private Channel[] channels;
	private int[] order;
	private Channel selectedChannel = null;
	private int selectedPoint = -1;
	private Vector2f mouse = new Vector2f();
	private boolean mouseDragging;
	
	public final static class Channel {
		private final ColorRGBA color;
		private final String name;
		private final int textureIndex;
		private final int textureChannel;
		private final ArrayList<Vector2f> points;
		
		private Node node;
		private Mesh[] meshes = new Mesh[2];
		private Geometry[] geoms = new Geometry[2];
		private VertexBuffer buffer;
		private TransferFunctionEditor tfe;

		/**
		 * A channel of the transfer function
		 * @param color the displayed color
		 * @param name displayed name of this channel
		 * @param textureIndex the index of the texture
		 * @param textureChannel the channel inside the texture
		 */
		public Channel(ColorRGBA color, String name, int textureIndex, int textureChannel) {
			this.color = color;
			this.name = name;
			this.textureIndex = textureIndex;
			this.textureChannel = textureChannel;
			this.points = new ArrayList<>();
			reset();
		}
		
		public void reset() {
			points.clear();
			points.add(new Vector2f(0, 0));
			points.add(new Vector2f(1, 1));
		}
		
		private void init(TransferFunctionEditor tfe, Node node) {
			this.tfe = tfe;
			buffer = new VertexBuffer(VertexBuffer.Type.Position);
			buffer.setupData(VertexBuffer.Usage.Dynamic, 4, VertexBuffer.Format.Float, BufferUtils.createFloatBuffer(4 * points.size()));

			this.node = new Node(name);
			node.attachChild(this.node);
			
			Mesh mesh1 = new Mesh();
			mesh1.setMode(Mesh.Mode.LineStrip);
			mesh1.setBuffer(buffer);
			Material mat1 = new Material(tfe.app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
			mat1.setColor("Color", color);
			mat1.getAdditionalRenderState().setLineWidth(LINE_WIDTHS[0]);
			Geometry geom1 = new Geometry(name, mesh1);
			geom1.setMaterial(mat1);
			geom1.setLocalScale(1, Y_SIZE, 1);
			this.node.attachChild(geom1);
			meshes[0] = mesh1;
			geoms[0] = geom1;
			
			Mesh mesh2 = new Mesh();
			mesh2.setMode(Mesh.Mode.Points);
			mesh2.setBuffer(buffer);
			Material mat2 = new Material(tfe.app.getAssetManager(), "org/shaman/jmecl/utils/UnshadedPoint.j3md");
			mat2.getAdditionalRenderState().setPointSprite(true);
			mat2.setColor("Color", color);
			Geometry geom2 = new Geometry(name+"_p", mesh2);
			geom2.setMaterial(mat2);
			geom2.setLocalScale(1, Y_SIZE, 1);
			this.node.attachChild(geom2);
			meshes[1] = mesh2;
			geoms[1] = geom2;
		}
		
		private void updateMesh() {
			geoms[0].getMaterial().getAdditionalRenderState().setLineWidth((tfe.selectedChannel == this) ? LINE_WIDTHS[1] : LINE_WIDTHS[0]);
			FloatBuffer data = BufferUtils.createFloatBuffer(4 * points.size());
			for (int i=0; i<points.size(); ++i) {
				Vector2f p = points.get(i);
				data.put(p.x);
				data.put(p.y);
				data.put(0);
				if (tfe.selectedChannel == this) {
					if (tfe.selectedPoint == i) {
						data.put(POINT_SIZES[2]);
					} else {
						data.put(POINT_SIZES[1]);
					}
				} else {
					data.put(POINT_SIZES[0]);
				}
			}
			data.flip();
			buffer.updateData(data);
			meshes[0].updateCounts();
			meshes[1].updateCounts();
		}
		
		private void updateTexture(ImageRaster raster) {
			for (int i=1; i<points.size(); ++i) {
				Vector2f p1 = points.get(i-1);
				Vector2f p2 = points.get(i);
				int x1 = Math.max(0, (int) Math.floor(p1.x * RESOLUTION));
				int x2 = Math.min(RESOLUTION-1, (int) Math.ceil(p2.x * RESOLUTION));
				ColorRGBA c = new ColorRGBA();
				for (int x=x1; x<=x2; ++x) {
					float v = p1.y + (p2.y - p1.y) * ((x-x1)/(float)(x2-x1));
					c = raster.getPixel(x, 0, c);
					switch (textureChannel) {
						case 0: c.r = v; break;
						case 1: c.g = v; break;
						case 2: c.b = v; break;
						case 3: c.a = v; break;
					}
					raster.setPixel(x, 0, c);
				}
			}
		}
	}

	public TransferFunctionEditor() {
		this(true);
	}

	public TransferFunctionEditor(boolean hasAlphaChannel) {
		if (hasAlphaChannel) {
			channels = new Channel[4];
		} else {
			channels = new Channel[3];
		}
		order = new int[channels.length];
		for (int i=0; i<channels.length; ++i) {
			channels[i] = new Channel(CHANNEL_COLORS[i], CHANNEL_NAMES[i], 0, i);
			order[i] = i;
		}
	}
	
	public TransferFunctionEditor(Channel[] channels) {
		this.channels = channels;
		order = new int[channels.length];
		for (int i=0; i<channels.length; ++i) {
			order[i] = i;
		}
	}
	
	public void initialize(Application app) {
		this.app = app;
		setEnabled(true);
		
		//add input mappings
		app.getInputManager().addMapping(MOUSE_MOVE_Xn, new MouseAxisTrigger(MouseInput.AXIS_X, true));
		app.getInputManager().addMapping(MOUSE_MOVE_Xp, new MouseAxisTrigger(MouseInput.AXIS_X, false));
		app.getInputManager().addMapping(MOUSE_MOVE_Yn, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
		app.getInputManager().addMapping(MOUSE_MOVE_Yp, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
		app.getInputManager().addMapping(MOUSE_CLICK_LEFT, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
		app.getInputManager().addMapping(MOUSE_CLICK_RIGHT, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
		listener = new Listener();
		app.getInputManager().addListener(listener, MOUSE_MOVE_Xn, MOUSE_MOVE_Xp, MOUSE_MOVE_Yn, MOUSE_MOVE_Yp, MOUSE_CLICK_LEFT, MOUSE_CLICK_RIGHT);
		
		//create textures
		int maxIndex = 0;
		for (Channel c : channels) {
			maxIndex = Math.max(maxIndex, c.textureIndex);
		}
		textures = new Texture[maxIndex+1];
		for (int i=0; i<=maxIndex; ++i) {
			ByteBuffer data = BufferUtils.createByteBuffer(4 * RESOLUTION);
			Image image = new Image(Image.Format.ARGB8, RESOLUTION, 1, data, ColorSpace.Linear);
			textures[i] = new Texture2D(image);
		}
		
		node = new Node("TransferFunctionEditor");
		
		//border
		Mesh border = new Mesh();
		border.setMode(Mesh.Mode.LineLoop);
		border.setBuffer(VertexBuffer.Type.Position, 2, new float[]{0,0, 1,0, 1,Y_SIZE, 0,Y_SIZE});
		Material boderMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
		boderMat.setColor("Color", ColorRGBA.White);
		boderMat.getAdditionalRenderState().setLineWidth(LINE_WIDTHS[0]);
		Geometry borderGeom = new Geometry("border", border);
		borderGeom.setMaterial(boderMat);
		borderGeom.setLocalTranslation(0, 0, -1);
		node.attachChild(borderGeom);
		
		for (Channel c : channels) {
			c.init(this, node);
		}
		
		node.setQueueBucket(RenderQueue.Bucket.Gui);
		updateMeshes();
		updateTexture();
	}
	
	private void updateMeshes() {
		for (Channel c : channels) {
			c.updateMesh();
		}
		for (int i=0; i<order.length; ++i) {
			channels[order[i]].node.setLocalTranslation(0, 0, order.length-i);
		}
	}
	
	private void updateTexture() {
		for (int i=0; i<textures.length; ++i) {
			ImageRaster raster = ImageRaster.create(textures[i].getImage());
			for (Channel c : channels) {
				if (c.textureIndex == i) {
					c.updateTexture(raster);
				}
			}
		}
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}
	
	public Spatial getView() {
		if (app == null) {
			throw new IllegalStateException("not initialized yet");
		}
		return node;
	}
	
	public Texture[] getTextures() {
		if (app == null) {
			throw new IllegalStateException("not initialized yet");
		}
		return textures;
	}
	
	public void reset() {
		for (Channel c : channels) {
			c.reset();
		}
		updateMeshes();
		updateTexture();
	}
	
	private void drag() {
		assert (selectedChannel != null);
		assert (selectedPoint >= 0);
		
		Vector2f newPos = new Vector2f(mouse);
		newPos.x /= node.getWorldScale().x;
		newPos.y /= node.getWorldScale().y * Y_SIZE;
		
		newPos.y = Math.max(0, Math.min(1, newPos.y));
		if (selectedPoint == 0) {
			newPos.x = 0;
		} else if (selectedPoint == selectedChannel.points.size()-1) {
			newPos.x = 1;
		} else {
			float xLeft = selectedChannel.points.get(selectedPoint-1).x;
			float xRight = selectedChannel.points.get(selectedPoint+1).x;
			newPos.x = Math.max(xLeft + (1f/RESOLUTION), Math.min(xRight - (1f/RESOLUTION), newPos.x));
		}
		
		selectedChannel.points.get(selectedPoint).set(newPos);
//		System.out.println("set to "+newPos);
		
		updateMeshes();
		updateTexture();
	}
	
	private boolean checkClick(boolean rightClick) {
		Vector2f size = new Vector2f(node.getWorldScale().x, node.getWorldScale().y * Y_SIZE);
		int maxPointDistance = POINT_SIZES[2];
		maxPointDistance *= maxPointDistance;
		int maxLineDistance = LINE_WIDTHS[1];
		maxLineDistance *= maxLineDistance;
		
		int selectedChannelIndex = -1;
		int selectedChannelIndex2 = -1;
		selectedPoint = -1;
		found:
		for (int i=0; i<order.length; ++i) {
			int j = order[i];
			//check for collision with that channel
			for (int k=0; k<channels[j].points.size(); ++k) {
				Vector2f p = channels[j].points.get(k);
				if (mouse.distanceSquared(p.x*size.x, p.y*size.y) <= maxPointDistance) {
					selectedChannelIndex = j;
					selectedChannelIndex2 = i;
					selectedPoint = k;
					break found;
				}
			}
			for (int k=1; k<channels[j].points.size(); ++k) {
				Vector2f p1 = channels[j].points.get(k-1).clone();
				p1.x *= size.x; p1.y *= size.y;
				Vector2f p2 = channels[j].points.get(k).clone();
				p2.x *= size.x; p2.y *= size.y;
				if (minimum_distance(p1, p2, mouse) <= maxLineDistance) {
					selectedChannelIndex = j;
					selectedChannelIndex2 = i;
					break found;
				}
			}
		}
		
		if (selectedChannelIndex == -1) {
			selectedChannel = null;
			updateMeshes();
//			System.out.println("nothing found");
			return false; //nothing found
		}
		
		if (rightClick) {
			if (selectedPoint == -1) {
				//create new point
				Vector2f newPos = new Vector2f(mouse);
				newPos.x /= node.getWorldScale().x;
				newPos.y /= node.getWorldScale().y * Y_SIZE;
				newPos.y = Math.max(0, Math.min(1, newPos.y));
				for (int i=1; i<channels[selectedChannelIndex].points.size(); ++i) {
					Vector2f p1 = channels[selectedChannelIndex].points.get(i-1);
					Vector2f p2 = channels[selectedChannelIndex].points.get(i);
					if (newPos.x > p1.x && newPos.x < p2.x) {
						channels[selectedChannelIndex].points.add(i, newPos);
						selectedPoint = i;
						break;
					}
				}
			} else {
				//delete selected point
				if (selectedPoint>0 && selectedPoint<channels[selectedChannelIndex].points.size()-1) {
					channels[selectedChannelIndex].points.remove(selectedPoint);
					selectedPoint = -1;
				}
			}
		}
		
		//bring selected channel to the top
		for (int i=selectedChannelIndex2; i>0; --i) {
			order[i] = order[i-1];
		}
		order[0] = selectedChannelIndex;
		selectedChannel = channels[selectedChannelIndex];
		
		updateMeshes();
		
//		System.out.println("selected channel="+selectedChannel.name+" (index="+selectedChannelIndex+", selected point="+selectedPoint);
		
		return selectedPoint >= 0;
	}
	//http://stackoverflow.com/a/1501725
	float minimum_distance(Vector2f v, Vector2f w, Vector2f p) {
		// Return minimum distance between line segment vw and point p
		float l2 = v.distanceSquared(w);  // i.e. |w-v|^2 -  avoid a sqrt
		if (l2 == 0.0) {
			return p.distance(v);   // v == w case
		} 
		// Consider the line extending the segment, parameterized as v + t (w - v).
		// We find projection of point p onto the line. 
		// It falls where t = [(p-v) . (w-v)] / |w-v|^2
		// We clamp t from [0,1] to handle points outside the segment vw.
		float t = Math.max(0, Math.min(1, (p.subtract(v)).dot(w.subtract(v)) / l2));
		Vector2f projection = v.add(w.subtract(v).multLocal(t));  // Projection falls on the segment
		return p.distance(projection);
	}
	
	private void release() {

	}
	
	@Override
	public void write(JmeExporter je) throws IOException {
		throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
	}

	@Override
	public void read(JmeImporter ji) throws IOException {
		throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
	}
	
	private class Listener implements AnalogListener, ActionListener {

		@Override
		public void onAnalog(String string, float f, float f1) {
			if (!enabled || app==null) {
				return;
			}
			if (!string.startsWith(KEY_PREFIX)) {
				return;
			}
			mouse.set(app.getInputManager().getCursorPosition());
			mouse.x -= node.getWorldTranslation().x;
			mouse.y -= node.getWorldTranslation().y;
			if (mouseDragging) {
				drag();
			}
		}

		@Override
		public void onAction(String string, boolean bln, float f) {
			if (!enabled || app==null) {
				return;
			}
			if (MOUSE_CLICK_LEFT.equals(string)) {
				if (bln) {
					mouseDragging = checkClick(false);
				} else {
					if (mouseDragging) {
						release();
						mouseDragging = false;
					}
				}
			} else if (MOUSE_CLICK_RIGHT.equals(string)) {
				if (bln) {
					mouseDragging = checkClick(true);
				} else {
					if (mouseDragging) {
						release();
						mouseDragging = false;
					}
				}
			}
		}
	}
}
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.font.Rectangle;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.opencl.CommandQueue;
import com.jme3.opencl.Context;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.shape.Quad;
import java.util.ArrayList;
import org.shaman.jmecl.OpenCLSettings;
import org.shaman.jmecl.fluids.DebugTools;
import org.shaman.jmecl.fluids.FlagGrid;
import org.shaman.jmecl.fluids.FluidSolver;
import org.shaman.jmecl.fluids.Grid;
import org.shaman.jmecl.fluids.MACGrid;
import org.shaman.jmecl.fluids.RealGrid;
import org.shaman.jmecl.utils.DebugContextFactory;
import org.shaman.jmecl.utils.LoggingContextFactory;
import org.shaman.jmecl.utils.SharedTexture;
import org.shaman.jmecl.utils.TransferFunctionEditor;
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.shaman.jmecl.test;


/**
 *
 * @author Sebastian
 */
public abstract class AbstractFluidTest2D extends SimpleApplication {
	protected static final int X_OFFSET = 250;
	private static final String KEY_PREFIX = "AFT2D_";
	private static final String KEY_RUN = KEY_PREFIX+"Run";
	
	protected Context clContext;
	protected CommandQueue clCommandQueue;
	protected OpenCLSettings clSettings;
	
	private boolean debugContext;
	private boolean loggingContext;
	private boolean recording;
	private boolean running;
	
	private boolean initialized;
	private int resolutionX;
	private int resolutionY;
	protected FluidSolver solver;

	private static class GridWithName<T extends Grid> {
		private final T grid;
		private final String name;
		private GridWithName(T grid, String name) {
			this.grid = grid;
			this.name = name;
		}
	}
	private ArrayList<GridWithName<RealGrid>> realGrids;
	private int realGridSelection = -1;
	private float realGridScale = 1;
	private ArrayList<GridWithName<MACGrid>> macGrids;
	private int macGridSelection = -1;
	private float macGridScale = 1;
	private ArrayList<GridWithName<FlagGrid>> flagGrids;
	private int flagGridSelection = -1;
	
	private boolean updateText = true;
	private BitmapText infoText;
	
	private DebugTools debugTools;
	private Geometry boundsGeometry;
	private SharedTexture realTexture;
	private Material realMaterial;
	private Geometry realGeometry;
	private SharedTexture flagTexture;
	private Material flagMaterial;
	private Geometry flagGeometry;
	private TransferFunctionEditor tfe;
	
	public AbstractFluidTest2D() {
	}

	public boolean isDebugContext() {
		return debugContext;
	}

	protected final void setDebugContext(boolean debugContext) {
		if (initialized) {
			throw new IllegalStateException("solver already initialized");
		}
		this.debugContext = debugContext;
	}

	public boolean isLoggingContext() {
		return loggingContext;
	}

	protected final void setLoggingContext(boolean loggingContext) {
		if (initialized) {
			throw new IllegalStateException("solver already initialized");
		}
		this.loggingContext = loggingContext;
	}

	public int getResolutionX() {
		return resolutionX;
	}

	protected final void setResolutionX(int resolutionX) {
		if (initialized) {
			throw new IllegalStateException("solver already initialized");
		}
		this.resolutionX = resolutionX;
	}

	public int getResolutionY() {
		return resolutionY;
	}

	protected final void setResolutionY(int resolutionY) {
		if (initialized) {
			throw new IllegalStateException("solver already initialized");
		}
		this.resolutionY = resolutionY;
	}

	@Override
	public final void simpleInitApp() {
		clContext = context.getOpenCLContext();
		if (debugContext) {
			clContext = DebugContextFactory.createDebugContext(clContext);