/***********************************************************************
Copyright (c) 2008, 2009, Memo Akten, www.memo.tv
*** The Mega Super Awesome Visuals Company ***
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of MSA Visuals nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***********************************************************************/
package com.nativelibs4java.opencl.demos.particles;
import com.nativelibs4java.opencl.*;
import com.nativelibs4java.opencl.CLMem.Usage;
import com.nativelibs4java.opencl.demos.JavaCLSettingsPanel;
import com.nativelibs4java.opencl.demos.SetupUtils;
import com.nativelibs4java.util.*;
import java.util.logging.*;
import javax.swing.*;
import org.bridj.Pointer;
import static org.bridj.Pointer.*;
import javax.media.opengl.*;
import static javax.media.opengl.GL.*;
import javax.media.opengl.awt.*;
import com.jogamp.opengl.util.*;
import com.jogamp.common.nio.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Random;
import javax.imageio.ImageIO;
import static javax.media.opengl.GL.*;
import javax.media.opengl.glu.GLU;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.basic.BasicMenuUI.ChangeHandler;
/**
*
* @author Olivier (ported to JavaCL/OpenCL4Java)
*/
public class ParticlesDemo implements GLEventListener {
public static Component createGLCanvas(int width, int height, boolean useSwing) {
GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
GLCapabilities caps = new GLCapabilities(GLProfile.get(GLProfile.GL2));
Component canvas = useSwing ? new GLJPanel(caps) : new GLCanvas(caps);
canvas.setSize(width, height);
canvas.setIgnoreRepaint(true);
canvas.setPreferredSize(new Dimension(width, height));
canvas.setSize(new Dimension(width, height));
return canvas;
}
static File lastFile;
volatile boolean paused;
final static float DEFAULT_MOUSE_WEIGHT = 0.7f;
volatile float mouseWeight = DEFAULT_MOUSE_WEIGHT;
static class ParticlesCountItem {
public int count;
public String string;
public ParticlesCountItem(int count, String string) {
this.count = count;
this.string = string;
}
@Override
public String toString() {
return string;
}
}
public static void main(String[] args) {
try {
System.setProperty("sun.java2d.noddraw","true");
try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception ex) {}
SetupUtils.failWithDownloadProposalsIfOpenCLNotAvailable();
JFrame f = new JFrame("JavaCL Particles Demo");
Box tb = Box.createHorizontalBox();
final JButton openImage = new JButton("Import"), saveImage = new JButton("Export"), changeBlend = new JButton("Change Blend");
tb.add(openImage);
//tb.add(saveImage);
tb.add(changeBlend);
//final JCheckBox limi
final AssertionError[] err = new AssertionError[1];
final ParticlesDemo demo = new ParticlesDemo();
final int nSpeeds = 21;
final JSlider speedSlider = new JSlider(0, nSpeeds - 1);
speedSlider.setValue(nSpeeds / 2);
//f.getContentPane().add("West", slider);
tb.add(speedSlider);
//slider.setOrientation(JSlider.VERTICAL);
speedSlider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int d = speedSlider.getValue() - nSpeeds / 2;
demo.speedFactor = (d == 0 ? 1 : d > 0 ? d : -1f/d) * DEFAULT_SPEED_FACTOR;
}
});
changeBlend.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
demo.iBlend = (demo.iBlend + 1) % demo.blends.length;
}
});
final Component canvas = createGLCanvas(1000, 800, demo.settings.isDirectGLRendering());
f.setLayout(new BorderLayout());
f.add("Center", canvas);
saveImage.addActionListener(new ActionListener() {
@SuppressWarnings("deprecation")
@Override
public void actionPerformed(ActionEvent ae) {
boolean paused = demo.paused;
demo.paused = true;
BufferedImage im = new BufferedImage(canvas.getWidth(), canvas.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics g = im.createGraphics();
canvas.paint(g);
g.dispose();
FileDialog fc = new FileDialog((Frame)null);
fc.setMode(FileDialog.SAVE);
fc.show();
if (fc.getFile() != null) {
try {
ImageIO.write(im, "jpeg", lastFile = new File(new File(fc.getDirectory()), fc.getFile()));
} catch (Exception ex) {
ParticlesDemo.exception(ex);
Logger.getLogger(ParticlesDemo.class.getName()).log(Level.SEVERE, null, ex);
}
}
demo.paused = paused;
}
});
openImage.addActionListener(new ActionListener() {
@SuppressWarnings("deprecation")
@Override
public void actionPerformed(ActionEvent ae) {
boolean paused = demo.paused;
demo.paused = true;
FileDialog fc = new FileDialog((Frame)null);
fc.setMode(FileDialog.LOAD);
fc.show();
if (fc.getFile() != null) {
try {
BufferedImage im = ImageIO.read(lastFile = new File(new File(fc.getDirectory()), fc.getFile()));
demo.setImage(im);
} catch (Exception ex) {
ParticlesDemo.exception(ex);
Logger.getLogger(ParticlesDemo.class.getName()).log(Level.SEVERE, null, ex);
}
}
demo.paused = paused;
}
});
canvas.addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getUnitsToScroll() > 0)
for (int i = e.getUnitsToScroll(); i-- != 0;)
demo.mouseWeight *= 1.1f;
else
for (int i = -e.getUnitsToScroll(); i-- != 0;)
demo.mouseWeight /= 1.1f;
}
});
canvas.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent ke) {
switch (ke.getKeyCode()) {
case KeyEvent.VK_SPACE:
demo.paused = !demo.paused;
break;
case KeyEvent.VK_DELETE:
case KeyEvent.VK_BACK_SPACE:
demo.mouseWeight = 1;
break;
}
}
});
final JSlider sliderMass = new JSlider(0, nSpeeds - 1);
sliderMass.setValue(nSpeeds / 2);
//f.getContentPane().add("East", sliderMass);
//sliderMass.setOrientation(JSlider.VERTICAL);
sliderMass.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int d = sliderMass.getValue() - nSpeeds / 2;
demo.massFactor = (d == 0 ? 1 : d > 0 ? d : -1f/d) * DEFAULT_MASS_FACTOR;
}
});
tb.add(sliderMass);
f.add("North", tb);
canvas.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent me) {
if (me.getButton() != MouseEvent.BUTTON1 || me.isMetaDown() || me.isControlDown())
demo.mouseWeight = 1;
else
demo.paused = !demo.paused;
}
@Override
public void mouseExited(MouseEvent e) {
demo.hasMouse = false;
}
@Override
public void mouseEntered(MouseEvent e) {
demo.hasMouse = true;
}
});
canvas.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
demo.mouseX = e.getX();
demo.mouseY = e.getY();
demo.lastMouseMove = System.currentTimeMillis();
}
});
//f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.pack();
f.setVisible(true);
FPSAnimator animator;
if (canvas instanceof GLCanvas) {
((GLCanvas)canvas).addGLEventListener(demo);
animator = new FPSAnimator(((GLCanvas)canvas), 30);
} else {
((GLJPanel)canvas).addGLEventListener(demo);
animator = new FPSAnimator(((GLJPanel)canvas), 30);
}
//animator.setRunAsFastAsPossible(true);
animator.start();
} catch (Exception ex) {
exception(ex);
Logger.getLogger(ParticlesDemo.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void setImage(BufferedImage image) {
mouseWeight = DEFAULT_MOUSE_WEIGHT;
int iWidth = image.getWidth(), iHeight = image.getHeight();
int[] pixels = image.getRGB(0, 0, iWidth, iHeight, null, 0, iWidth);
int nPixels = iWidth * iHeight;
float[] nonEmptyPixelsX = new float[nPixels], nonEmptyPixelsY = new float[nPixels];
int[] nonEmptyPixels = new int[nPixels];
int nNonEmptyPixels = 0;
int hw = iWidth / 2, hh = iHeight / 2;
for (int iPixel = 0; iPixel < nPixels; iPixel++) {
int pixel = pixels[iPixel];
if ((pixel & 0xff000000) != 0)
{
int y = iPixel / iWidth, x = iPixel - y * iWidth;
nonEmptyPixels[nNonEmptyPixels] = pixel;
nonEmptyPixelsX[nNonEmptyPixels] = x - hw;
nonEmptyPixelsY[nNonEmptyPixels] = hh - y;
nNonEmptyPixels++;
}
}
queue.finish();
Pointer<Float> positionsView = interleavedColorAndPositionsTemp.as(Float.class);
Pointer<Integer> colorView = interleavedColorAndPositionsTemp.as(Integer.class);
for (int iPoint = 0; iPoint < particlesCount; iPoint++) {
int iPixel = (int)(random.nextFloat() * (nNonEmptyPixels - 1));
velocities.set(iPixel, 0f);
velocities.set(iPixel + 1, 0f);
int colorOffset = iPoint * (elementSize / 4);
int posOffset = iPoint * (elementSize / 4) + 1;
colorView.set(colorOffset, nonEmptyPixels[iPixel]);
positionsView.set(posOffset, nonEmptyPixelsX[iPixel]);
positionsView.set(posOffset + 1, nonEmptyPixelsY[iPixel]);
}
velocitiesMem.write(queue, velocities, false);
if (useOpenGLContext)
interleavedColorAndPositionsMem.acquireGLObject(queue);
interleavedColorAndPositionsMem.write(queue, interleavedColorAndPositionsTemp, false);
if (useOpenGLContext)
interleavedColorAndPositionsMem.releaseGLObject(queue);
queue.finish();
}
CLContext context;
CLQueue queue;
boolean useOpenGLContext = false;
int particlesCount;
int[] vbo = new int[1];
static final float DEFAULT_SLOWDOWN_FACTOR = 0.7f;
static final float DEFAULT_SPEED_FACTOR = 2f, DEFAULT_MASS_FACTOR = 2;
float mouseX, mouseY, width, height, massFactor = DEFAULT_MASS_FACTOR, speedFactor = DEFAULT_SPEED_FACTOR, slowDownFactor = DEFAULT_SLOWDOWN_FACTOR;
boolean hasMouse = false;
boolean limitToScreen = false;
long lastMouseMove;
Pointer<Float> velocities;
//CLKernel updateParticleKernel;
ParticlesDemoProgram particlesProgram;
CLBuffer<Float> massesMem, velocitiesMem;
CLBuffer<Byte> interleavedColorAndPositionsMem;
Pointer<Byte> interleavedColorAndPositionsTemp;
int elementSize = 4 * 4;//4 + 2 * 4 + 4; // 4 color bytes and 2 position floats, 1 dummy alignment float
CLBuffer<Byte> colorsMem;
Random random = new Random(System.nanoTime());
JavaCLSettingsPanel settings = new JavaCLSettingsPanel();
public ParticlesDemo() {
ParticlesCountItem[] items = new ParticlesCountItem[] {
new ParticlesCountItem(1024, "1K"),
new ParticlesCountItem(1024 * 10,"10K"),
new ParticlesCountItem(1024 * 100,"100K"),
new ParticlesCountItem(1024 * 1000,"1M"),
new ParticlesCountItem(1024 * 10000,"10M")
};
JComboBox cb = new JComboBox(items);
cb.setSelectedIndex(2);
JLabel lb = new JLabel("Number of particles");
Box countPanel = Box.createHorizontalBox();
SetupUtils.setEtchedTitledBorder(countPanel, "Particles Demo Settings");
countPanel.add(lb);
countPanel.add(Box.createHorizontalStrut(5));
countPanel.add(cb);
cb.setMinimumSize(new Dimension(100, 10));
cb.setMaximumSize(new Dimension(200, Integer.MAX_VALUE));
countPanel.add(Box.createHorizontalGlue());
//sett.removeOpenGLComponents();
final JPanel opts = new JPanel(new BorderLayout());
JLabel detailsLab = new JLabel("<html><body><a href='#'>Advanced OpenCL settings...</a></body>", JLabel.RIGHT);
detailsLab.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
opts.add("Center", detailsLab);
detailsLab.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
opts.removeAll();
opts.add("Center", settings);
opts.invalidate();
Component c = opts.getParent();
while (c != null) {
if (c instanceof Frame) {
((Frame)c).pack();
break;
}
if (c instanceof JDialog) {
((JDialog)c).pack();
break;
}
c = c.getParent();
}
}
});
int opt = JOptionPane.showConfirmDialog(null, new Object[] { countPanel, opts }, "JavaCL Particles Demo", JOptionPane.OK_CANCEL_OPTION);
if (opt != JOptionPane.OK_OPTION)
System.exit(0);
useOpenGLContext = settings.isGLSharingEnabled();
particlesCount = ((ParticlesCountItem)cb.getSelectedItem()).count;
}
int[] blends = new int[] {
GL_ONE_MINUS_SRC_ALPHA,
GL_ONE,
GL_ONE_MINUS_DST_ALPHA,
GL_ONE_MINUS_DST_COLOR,
GL_ONE_MINUS_SRC_COLOR,
GL_SRC_ALPHA,
GL_DST_ALPHA,
GL_SRC_COLOR,
GL_DST_COLOR,
};
volatile int iBlend = 0;
public static void exception(Throwable ex) {
StringWriter sout = new StringWriter();
ex.printStackTrace(new PrintWriter(sout));
JOptionPane.showMessageDialog(null, sout.toString(), "[Error] " + ParticlesDemo.class.getSimpleName() + " JavaCL Demo", JOptionPane.ERROR_MESSAGE);
}
@Override
public void init(GLAutoDrawable glad) {
try {
GL2 gl = (GL2)glad.getGL();
gl.glClearColor(0, 0, 0, 1);
gl.glClear(GL_COLOR_BUFFER_BIT);
//gl.glViewport(0, 0, (int)width, (int)height);
gl.glEnable(GL_BLEND);
gl.glEnable(GL2.GL_POINT_SMOOTH);
try {
if (useOpenGLContext) {
context = JavaCL.createContextFromCurrentGL();
}
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(null, "Sharing of context between OpenCL and OpenGL failed.\n" +
"The demo will run fine without anyway.", "JavaCL Demo", JOptionPane.OK_OPTION);
}
if (context == null) {
useOpenGLContext = false;
CLDevice device = settings.getDevice();
if (device == null)
device = JavaCL.getBestDevice();
context = JavaCL.createContext(null, device);
}
queue = context.createDefaultQueue();
Pointer<Float> masses = allocateFloats(particlesCount).order(context.getByteOrder());
velocities = allocateFloats(2 * particlesCount).order(context.getByteOrder());
interleavedColorAndPositionsTemp = allocateBytes(elementSize * particlesCount).order(context.getByteOrder());
Pointer<Float> positionsView = interleavedColorAndPositionsTemp.as(Float.class);
for (int i = 0; i < particlesCount; i++) {
masses.set(i, 0.5f + 0.5f * random.nextFloat());
velocities.set(i * 2, (random.nextFloat() - 0.5f) * 0.2f);
velocities.set(i * 2 + 1, (random.nextFloat() - 0.5f) * 0.2f);
int colorOffset = i * elementSize;
int posOffset = i * (elementSize / 4) + 1;
byte r = (byte)128, g = r, b = r, a = r;
interleavedColorAndPositionsTemp.set(colorOffset++, r);
interleavedColorAndPositionsTemp.set(colorOffset++, g);
interleavedColorAndPositionsTemp.set(colorOffset++, b);
interleavedColorAndPositionsTemp.set(colorOffset, a);
float x = (random.nextFloat() - 0.5f) * 200,
y = (random.nextFloat() - 0.5f) * 200;
positionsView.set(posOffset, (float)x);
positionsView.set(posOffset + 1, y);
}
velocitiesMem = context.createBuffer(Usage.InputOutput, velocities, false);
massesMem = context.createBuffer(Usage.Input, masses, true);
gl.glGenBuffers(1, vbo, 0);
gl.glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
gl.glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
gl.glBufferData(GL_ARRAY_BUFFER, (int) interleavedColorAndPositionsTemp.getValidBytes(), interleavedColorAndPositionsTemp.getByteBuffer(), GL2.GL_DYNAMIC_COPY);
gl.glBindBuffer(GL_ARRAY_BUFFER, 0);
if (useOpenGLContext) {
interleavedColorAndPositionsMem = context.createBufferFromGLBuffer(Usage.InputOutput, vbo[0]);
} else
interleavedColorAndPositionsMem = context.createBuffer(Usage.InputOutput, interleavedColorAndPositionsTemp, false);
String hsv2rgbSrc = IOUtils.readText(ParticlesDemo.class.getResourceAsStream("HSVtoRGB.c"));
//String src = IOUtils.readText(ParticlesDemo.class.getResourceAsStream("ParticlesDemo.c"));
CLProgram program = context.createProgram(hsv2rgbSrc);
particlesProgram = new ParticlesDemoProgram(program);
//updateParticleKernel = program.build().createKernel("updateParticle");
updateKernelArgs();
gl.glPointSize(2f);
} catch (Exception ex) {
Logger.getLogger(ParticlesDemo.class.getName()).log(Level.SEVERE, null, ex);
ex.printStackTrace();
exception(ex);
System.exit(1);
}
}
@Override
public void dispose(GLAutoDrawable glad) {
}
@Override
public void display(GLAutoDrawable glad) {
GL2 gl = (GL2)glad.getGL();
gl.glBlendFunc(GL_SRC_ALPHA, blends[iBlend]);
gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
new GLU().gluOrtho2D(-width / 2 - 1, width / 2 + 1, -height/2 - 1, height/2 + 1);
gl.glMatrixMode(GL2.GL_MODELVIEW);
gl.glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
if (useOpenGLContext) {
queue.finish();
} else {
//interleavedColorAndPositionsMem.map(queue, CLMem.MapFlags.Read);
interleavedColorAndPositionsMem.read(queue, interleavedColorAndPositionsTemp, true);
gl.glBufferSubData(GL_ARRAY_BUFFER, 0, (int)interleavedColorAndPositionsTemp.getValidBytes(), interleavedColorAndPositionsTemp.getByteBuffer());
//interleavedColorAndPositionsMem.unmap(queue, interleavedColorAndPositionsTemp);
}
gl.glClear(GL_COLOR_BUFFER_BIT);
gl.glColor3f(1.0f, 1.0f, 1.0f);
//gl.glEnableClientState(GL_VERTEX_ARRAY);
//gl.glEnableClientState(GL_COLOR_ARRAY);
//gl.glColorPointer(4, GL_UNSIGNED_BYTE, elementSize,
gl.glInterleavedArrays(GL2.GL_C4UB_V2F, elementSize, 0);
gl.glDrawArrays(GL_POINTS, 0, particlesCount);
gl.glBindBuffer(GL_ARRAY_BUFFER, 0);
if (!paused)
updateKernelArgs();
}
@Override
public void reshape(GLAutoDrawable glad, int x, int y, int width, int height) {
this.width = width;
this.height = height;
}
private synchronized void updateKernelArgs() {
if (useOpenGLContext)
interleavedColorAndPositionsMem.acquireGLObject(queue);
try {
CLEvent evt = particlesProgram.updateParticle(
queue,
massesMem,
velocitiesMem,
interleavedColorAndPositionsMem.as(Float.class),
new float[] {mouseX - width / 2f, height / 2f - mouseY},
new float[] {width, height},
massFactor,
speedFactor,
slowDownFactor,
hasMouse ? mouseWeight : 0,
(byte)(limitToScreen ? 1 : 0),
new int[] { particlesCount }, null
);
evt.release(); // the gc might be to slow to reclaim the event, so do manual memory management here
} catch (Throwable ex) {
exception(ex);
System.exit(1);
}
if (useOpenGLContext)
interleavedColorAndPositionsMem.releaseGLObject(queue);
}
}