package edu.stanford.rsl.conrad.opencl.rendering;
/*
* JCuda - Java bindings for NVIDIA CUDA driver and runtime API
* http://www.jcuda.org
*
* DISCLAIMER: THIS SOFTWARE IS PROVIDED WITHOUT WARRANTY OF ANY KIND
* If you find any bugs or errors, contact me at http://www.jcuda.org
*
* LICENSE: THIS SOFTWARE IS FREE FOR NON-COMMERCIAL USE ONLY
* For non-commercial applications, you may use this software without
* any restrictions. If you wish to use it for commercial purposes,
* contact me at http://www.jcuda.org
*/
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.media.opengl.*;
import javax.media.opengl.awt.GLJPanel;
import javax.swing.*;
import javax.swing.event.*;
import com.jogamp.opencl.CLBuffer;
import com.jogamp.opencl.CLCommandQueue;
import com.jogamp.opencl.CLDevice;
import com.jogamp.opencl.CLImage2d;
import com.jogamp.opencl.CLImage3d;
import com.jogamp.opencl.CLImageFormat;
import com.jogamp.opencl.CLKernel;
import com.jogamp.opencl.CLPlatform;
import com.jogamp.opencl.CLProgram;
import com.jogamp.opencl.CLImageFormat.ChannelOrder;
import com.jogamp.opencl.CLImageFormat.ChannelType;
import com.jogamp.opencl.CLMemory.Mem;
import com.jogamp.opencl.gl.CLGLBuffer;
import com.jogamp.opencl.gl.CLGLContext;
import com.jogamp.opengl.util.Animator;
import com.jogamp.opengl.util.awt.TextRenderer;
import edu.stanford.rsl.conrad.opencl.OpenCLUtil;
/**
* A sample illustrating how to use textures with JCuda. This program uses
* the CUBIN file that is created by the "volumeRender" program from the
* NVIDIA CUDA samples web site. <br />
* <br />
* The program loads an 8 bit RAW volume data set and copies it into a
* 3D texture. The texture is accessed by the kernel to render an image
* of the volume data. The resulting image is written into a pixel
* buffer object (PBO) which is then displayed using JOGL.
*/
public class OpenCLTextureRendering extends JFrame implements GLEventListener, MouseControlable
{
/**
*
*/
private static final long serialVersionUID = -6181935230597653012L;
/**
* Entry point for this sample.
*
* @param args not used
*/
public static void main(String args[])
{
startSample("Bucky.raw", 32, 32, 32, false);
// Other input files may be obtained from http://www.volvis.org
//startSample("mri_ventricles.raw", 256, 256, 124, false);
//startSample("backpack8.raw", 512, 512, 373, false);
//startSample("foot.raw", 256, 256, 256, false);
}
/**
* Starts this sample with the data that is read from the file
* with the given name. The data is assumed to have the
* specified dimensions.
*
* @param fileName The name of the volume data file to load
* @param sizeX The size of the data set in X direction
* @param sizeY The size of the data set in Y direction
* @param sizeZ The size of the data set in Z direction
* @param stereoMode Whether stereo mode should be used
*/
private static void startSample(
String fileName, final int sizeX, final int sizeY,
final int sizeZ, final boolean stereoMode)
{
// Try to read the specified file
byte data[] = null;
try
{
int size = sizeX * sizeY * sizeZ;
FileInputStream fis = new FileInputStream(fileName);
data = new byte[size];
fis.read(data);
}
catch (IOException e)
{
System.err.println("Could not load input file");
e.printStackTrace();
return;
}
// Start the sample with the data that was read from the file
final byte volumeData[] = data;
//SwingUtilities.invokeLater(new Runnable()
//{
//public void run()
//{
OpenCLTextureRendering sample = new OpenCLTextureRendering(
volumeData, sizeX, sizeY, sizeZ, stereoMode);
sample.start();
//}
//});
}
protected float transferFunc[] = new float[]
{
0.0f, 0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.5f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f, 1.0f,
0.0f, 0.0f, 0.0f, 0.0f
};
/**
* Whether the initialization method of this GLEventListener has already
* been called
*/
protected boolean initialized = false;
/**
* Whether the stereo mode is enabled
*/
private boolean stereoMode = false;
/**
* The GL component which is used for rendering
*/
private GLJPanel glComponentL;
/**
* The GL component which is used for the other eye
*/
private GLJPanel glComponentR;
/**
* Text renderer for status messages
*/
protected TextRenderer renderer;
/**
* The animator used for rendering
*/
Animator animatorL;
/**
* The animator for the other eye
*/
Animator animatorR;
/**
* The OpenCL context
*/
protected CLGLContext glCtx;
/**
* The OpenCL program
*/
protected CLProgram program;
/**
* The OpenCL device
*/
private CLDevice device;
/**
* The OpenCL kernel function binding
*/
protected CLKernel kernelFunction;
/**
* The OpenCL command queue
*/
protected CLCommandQueue commandQueue;
/**
* The 3D volume texture reference
*/
protected CLImage3d<IntBuffer> tex = null;
/**
* The 1D transfer texture reference
*/
protected CLImage2d<FloatBuffer> transferTex = null;
protected CLBuffer<IntBuffer> hvolumeBuffer = null;
protected CLBuffer<FloatBuffer> transferFctBuffer =null;
/**
* The volume data that is to be rendered
*/
protected byte[] h_volume;
/**
* The inverted view matrix, which will be copied to the global
* variable of the kernel.
*/
protected CLBuffer<FloatBuffer> invViewMatrix;
/**
* The output of CL
*/
protected CLGLBuffer<?> d_output;
/**
* The width of the rendered area and the PBO
*/
protected int width = 0;
/**
* The height of the rendered area and the PBO
*/
protected int height = 0;
/**
* The size of the volume data that is to be rendered
*/
protected int[] volumeSize = null;
/**
* The block size for the kernel execution
*/
protected static int blockSize[] = {16, 16};
/**
* The block size for the kernel execution
*/
protected float[] gridSize;
/**
* The density of the rendered volume data
*/
protected float density = 0.05f;
/**
* The brightness of the rendered volume data
*/
protected float brightness = 1.0f;
/**
* The transferOffset of the rendered volume data
*/
protected float transferOffset = 0.0f;
/**
* The transferScale of the rendered volume data
*/
protected float transferScale = 1.0f;
/**
* The OpenGL pixel buffer object
*/
int pbo = 0;
/**
* The translation in X-direction
*/
private float translationX = 0;
/**
* The translation in Y-direction
*/
private float translationY = 0;
/**
* The translation in Z-direction
*/
private float translationZ = -4;
/**
* The rotation about the X-axis, in degrees
*/
private float rotationX = 0;
protected JFrame frame = null;
/**
* The rotation about the Y-axis, in degrees
*/
private float rotationY = 0;
/**
* The System.nanoTime() of the previous rendered frame.
*/
private long prevFrameNanoTime = 0;
/**
* Creates a new JCudaTextureSample that displays the given volume
* data, which has the specified size.
*
* @param volumeData The volume data
* @param sizeX The size of the data set in X direction
* @param sizeY The size of the data set in Y direction
* @param sizeZ The size of the data set in Z direction
* @param stereoMode Whether stereo mode should be used
*/
public OpenCLTextureRendering(
byte volumeData[], int sizeX, int sizeY, int sizeZ, boolean stereoMode)
{
volumeSize = new int[3];
h_volume = volumeData;
volumeSize[0] = sizeX;
volumeSize[1] = sizeY;
volumeSize[2] = sizeZ;
this.stereoMode = stereoMode;
if (stereoMode)
{
width = 280;
height = 280;
}
else
{
width = 800;
height = 800;
}
// Create the main frame
frame = new JFrame("OpenCL 3D texture volume rendering");
//start();
}
public void start(){
// Initialize the GL component
glComponentL = new GLJPanel();
glComponentL.addGLEventListener(this);
if (stereoMode)
{
glComponentR = new GLJPanel();
glComponentR.addGLEventListener(this);
}
// Initialize the mouse controls
MouseControl mouseControl = new MouseControl(this);
glComponentL.addMouseMotionListener(mouseControl);
glComponentL.addMouseWheelListener(mouseControl);
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
runExit();
}
});
frame.setLayout(new BorderLayout());
glComponentL.setPreferredSize(new Dimension(width, height));
JPanel p = new JPanel(new GridLayout(1,1));
p.add(glComponentL);
if (stereoMode)
{
p.setLayout(new GridLayout(1,2));
p.add(glComponentR);
}
frame.add(p, BorderLayout.CENTER);
frame.add(this.createControlPanel(), BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
// Create and start the animator
boolean animate = true;
if (animate) {
animatorL = new Animator(glComponentL);
animatorL.setRunAsFastAsPossible(true);
animatorL.start();
if (stereoMode)
{
animatorR = new Animator(glComponentR);
animatorR.setRunAsFastAsPossible(true);
animatorR.start();
}
}
}
/**
* Create the control panel containing the sliders for setting
* the visualization parameters.
*
* @return The control panel
*/
protected JPanel createControlPanel()
{
JPanel controlPanel = new JPanel(new GridLayout(3, 2));
JPanel panel = null;
JSlider slider = null;
// Density
panel = new JPanel(new GridLayout(1, 2));
panel.add(new JLabel("Density:"));
slider = new JSlider(0, 100, 5);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
JSlider source = (JSlider) e.getSource();
float a = source.getValue() / 100.0f;
density = a;
}
});
slider.setPreferredSize(new Dimension(0, 0));
panel.add(slider);
controlPanel.add(panel);
// Brightness
panel = new JPanel(new GridLayout(1, 2));
panel.add(new JLabel("Brightness:"));
slider = new JSlider(0, 100, 10);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
JSlider source = (JSlider) e.getSource();
float a = source.getValue() / 100.0f;
brightness = a * 10;
}
});
slider.setPreferredSize(new Dimension(0, 0));
panel.add(slider);
controlPanel.add(panel);
// Transfer offset
panel = new JPanel(new GridLayout(1, 2));
panel.add(new JLabel("Transfer Offset:"));
slider = new JSlider(0, 1000, 550);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
JSlider source = (JSlider) e.getSource();
float a = source.getValue() / 1000.0f;
transferOffset = (-0.5f + a) * 2;
}
});
slider.setPreferredSize(new Dimension(0, 0));
panel.add(slider);
controlPanel.add(panel);
// Transfer scale
panel = new JPanel(new GridLayout(1, 2));
panel.add(new JLabel("Transfer Scale:"));
slider = new JSlider(0, 1000, 100);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
JSlider source = (JSlider) e.getSource();
float a = source.getValue() / 1000.0f;
transferScale = a * 5;
}
});
slider.setPreferredSize(new Dimension(0, 0));
panel.add(slider);
controlPanel.add(panel);
return controlPanel;
}
/**
* Implementation of GLEventListener: Called to initialize the
* GLAutoDrawable. This method will initialize the JCudaDriver
* and cause the initialization of CUDA and the OpenGL PBO.
*/
public void init(GLAutoDrawable drawable)
{
// Perform the default GL initialization
GL gl = drawable.getGL();
gl.setSwapInterval(0);
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
setupView(drawable);
// Initialize the GL_ARB_pixel_buffer_object extension
if (!gl.isExtensionAvailable("GL_ARB_pixel_buffer_object"))
{
new Thread(new Runnable()
{
public void run()
{
JOptionPane.showMessageDialog(null,
"GL_ARB_pixel_buffer_object extension not available",
"Unavailable extension", JOptionPane.ERROR_MESSAGE);
runExit();
}
}).start();
}
// Create a TextRenderer for the status messages
renderer = new TextRenderer(new Font("SansSerif", Font.PLAIN, 12));
if (initialized)
{
return;
}
try {
// find gl compatible device
device = OpenCLUtil.getStaticContext().getMaxFlopsDevice();
// create OpenCL context before creating any OpenGL objects
// you want to share with OpenCL (AMD driver requirement)
glCtx = CLGLContext.create(drawable.getContext(), device);
// create the program
program = glCtx.createProgram(getClass().getResourceAsStream("volumeRender.cl")).build();
System.out.println(program.getBuildStatus());
System.out.println(program.isExecutable());
System.out.println(program.getBuildLog());
commandQueue = device.createCommandQueue();
kernelFunction = program.createCLKernel("d_render");
} catch (Exception e) {
e.printStackTrace();
unload();
}
// Initialize CUDA with the current volume data
initCL();
// Initialize the OpenGL pixel buffer object
initPBO(gl);
initialized = true;
}
void initVolumeBuffer(){
hvolumeBuffer = glCtx.createIntBuffer(h_volume.length, Mem.READ_ONLY);
for (int i = 0; i < h_volume.length; i++) {
hvolumeBuffer.getBuffer().put(i, (int)h_volume[i]);
}
hvolumeBuffer.getBuffer().rewind();
CLImageFormat format = new CLImageFormat(ChannelOrder.RGBA, ChannelType.UNORM_INT8);
tex = glCtx.createImage3d(hvolumeBuffer.getBuffer(), volumeSize[0], volumeSize[1], volumeSize[2], format, Mem.READ_ONLY, Mem.ALLOCATE_BUFFER);
hvolumeBuffer.release();
try {
commandQueue.putWriteImage(tex, true)
.finish();
} catch (Exception e) {
e.printStackTrace();
unload();
}
hvolumeBuffer = null;
}
/**
* Initialize CUDA and the 3D texture with the current volume data.
*/
void initCL()
{
initVolumeBuffer();
transferFctBuffer = glCtx.createFloatBuffer(transferFunc.length, Mem.READ_ONLY);
transferFctBuffer.getBuffer().put(transferFunc);
transferFctBuffer.getBuffer().rewind();
CLImageFormat formatTransFct = new CLImageFormat(ChannelOrder.RGBA, ChannelType.FLOAT);
transferTex = glCtx.createImage2d(transferFctBuffer.getBuffer(), transferFunc.length/4, 1, formatTransFct, Mem.READ_ONLY, Mem.ALLOCATE_BUFFER);
transferFctBuffer.release();
try {
commandQueue.putWriteImage(transferTex, true)
.finish();
} catch (Exception e) {
e.printStackTrace();
unload();
}
invViewMatrix = glCtx.createFloatBuffer(12, Mem.READ_ONLY);
}
/**
* Creates a pixel buffer object (PBO) which stores the image that
* is created by the kernel, and which will later be rendered
* by JOGL.
*
* @param gl The GL context
*/
void initPBO(GL gl)
{
if (pbo != 0)
{
gl.glDeleteBuffers(1, new int[]{ pbo }, 0);
pbo = 0;
}
// Create and bind a pixel buffer object with the current
// width and height of the rendering component.
int pboArray[] = new int[1];
gl.glGenBuffers(1, pboArray, 0);
pbo = pboArray[0];
gl.glBindBuffer(GL4bc.GL_PIXEL_UNPACK_BUFFER, pbo);
gl.glBufferData(GL4bc.GL_PIXEL_UNPACK_BUFFER,
width * height * 4, null, GL.GL_DYNAMIC_DRAW);
gl.glBindBuffer(GL4bc.GL_PIXEL_UNPACK_BUFFER, 0);
d_output = glCtx.createFromGLBuffer(pbo,
width * height * 4,
CLGLBuffer.Mem.WRITE_ONLY);
}
/**
* Integral division, rounding the result to the next highest integer.
*
* @param a Dividend
* @param b Divisor
* @return a/b rounded to the next highest integer.
*/
int iDivUp(int a, int b)
{
return (a % b != 0) ? (a / b + 1) : (a / b);
}
/**
* Set up a default view for the given GLAutoDrawable
*
* @param drawable The GLAutoDrawable to set the view for
*/
protected void setupView(GLAutoDrawable drawable)
{
GL4bc gl = (GL4bc) drawable.getGL();
gl.glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight());
gl.glMatrixMode(GL4bc.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glMatrixMode(GL4bc.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(0.0, 1.0, 0.0, 1.0, 0.0, 1.0);
}
/**
* Returns the given (address) value, adjusted to have
* the given alignment. In newer versions of JCuda, this
* function is also available as JCudaDriver#align
*
* @param value The address value
* @param alignment The desired alignment
* @return The aligned address value
*/
protected static int align(int value, int alignment)
{
return (((value) + (alignment) - 1) & ~((alignment) - 1));
}
protected void setKernelParameters(){
// Set up the execution parameters for the kernel:
// - One pointer for the output that is mapped to the PBO
// - Two ints for the width and height of the image to render
// - Four floats for the visualization parameters of the renderer
kernelFunction.rewind()
.putArg(d_output)
.putArg(width)
.putArg(height)
.putArg(density)
.putArg(brightness)
.putArg(transferOffset)
.putArg(transferScale)
.putArg(invViewMatrix)
.putArg(tex)
.putArg(transferTex)
.rewind();
}
/**
* Call the kernel function, rendering the 3D volume data image
* into the PBO
*/
protected void render()
{
// Set up the execution parameters for the kernel:
setKernelParameters();
int[] realLocalSize = {Math.min(device.getMaxWorkGroupSize(),blockSize[0]), Math.min(device.getMaxWorkGroupSize(),blockSize[1])};
// rounded up to the nearest multiple of localWorkSize
int[] globalWorkSize = {OpenCLUtil.roundUp(realLocalSize[0], width), OpenCLUtil.roundUp(realLocalSize[1], height)};
try {
commandQueue.putAcquireGLObject(d_output)
.put2DRangeKernel(kernelFunction, 0, 0, globalWorkSize[0], globalWorkSize[1], 0, 0)
.putReleaseGLObject(d_output)
.finish();
} catch (Exception e) {
e.printStackTrace();
unload();
}
}
/**
* Implementation of GLEventListener: Called when the given GLAutoDrawable
* is to be displayed.
*/
public void display(GLAutoDrawable drawable)
{
if (!initialized)
{
return;
}
if (pbo == 0)
{
return;
}
GL4bc gl = (GL4bc) drawable.getGL();
float eyeDelta = 0;
if (stereoMode)
{
if (drawable == glComponentL)
{
eyeDelta = 4f;
}
else
{
eyeDelta = -4f;
}
}
// Use OpenGL to build view matrix
float modelView[] = new float[16];
gl.glMatrixMode(GL4bc.GL_MODELVIEW);
gl.glPushMatrix();
gl.glLoadIdentity();
gl.glRotatef(-rotationX, 1.0f, 0.0f, 0.0f);
gl.glRotatef(-(rotationY+eyeDelta), 0.0f, 1.0f, 0.0f);
gl.glTranslatef(-translationX, -translationY, -translationZ);
gl.glGetFloatv(GL4bc.GL_MODELVIEW_MATRIX, modelView, 0);
gl.glCullFace(GL.GL_FRONT_AND_BACK);
gl.glPopMatrix();
float[] loc_invViewMatrix = new float[12];
// Build the inverted view matrix
loc_invViewMatrix[0] = modelView[0];
loc_invViewMatrix[1] = modelView[4];
loc_invViewMatrix[2] = modelView[8];
loc_invViewMatrix[3] = modelView[12];
loc_invViewMatrix[4] = modelView[1];
loc_invViewMatrix[5] = modelView[5];
loc_invViewMatrix[6] = modelView[9];
loc_invViewMatrix[7] = modelView[13];
loc_invViewMatrix[8] = modelView[2];
loc_invViewMatrix[9] = modelView[6];
loc_invViewMatrix[10] = modelView[10];
loc_invViewMatrix[11] = modelView[14];
invViewMatrix.getBuffer().rewind();
invViewMatrix.getBuffer().put(loc_invViewMatrix);
invViewMatrix.getBuffer().rewind();
// Copy the inverted view matrix to the global variable that
// was obtained from the module. The inverted view matrix
// will be used by the kernel during rendering.
try {
commandQueue.putWriteBuffer(invViewMatrix, true)
.finish();
} catch (Exception e) {
e.printStackTrace();
unload();
}
// Render and fill the PBO with pixel data
render();
// Draw the image from the PBO
gl.glClear(GL.GL_COLOR_BUFFER_BIT);
gl.glDisable(GL.GL_DEPTH_TEST);
gl.glRasterPos2i(0, 0);
gl.glBindBuffer(GL4bc.GL_PIXEL_UNPACK_BUFFER, pbo);
gl.glDrawPixels(width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, 0);
gl.glBindBuffer(GL4bc.GL_PIXEL_UNPACK_BUFFER, 0);
if (!stereoMode)
{
// Compute FPS
long nanoTime = System.nanoTime();
double frameTimeMs = (nanoTime - prevFrameNanoTime) / 1000000.0;
prevFrameNanoTime = nanoTime;
double fps = 1000.0 / frameTimeMs;
String fpsString = String.format("%.2f", fps);
// Print status message
renderer.beginRendering(drawable.getSurfaceWidth(), drawable.getSurfaceHeight());
renderer.setColor(1.0f, 1.0f, 1.0f, 0.5f);
renderer.draw(fpsString + " fps", 20, 10);
renderer.endRendering();
}
}
/**
* release all CL related objects and free memory
*/
protected void unload(){
if (commandQueue != null)
commandQueue.release();
//release all buffers
if (tex != null)
tex.release();
if (transferTex != null)
transferTex.release();
if (invViewMatrix != null)
invViewMatrix.release();
if (d_output != null)
d_output.release();
if (hvolumeBuffer != null)
hvolumeBuffer.release();
if (kernelFunction != null)
kernelFunction.release();
if (program != null)
program.release();
if (glCtx != null)
glCtx.release();
}
/**
* Implementation of GLEventListener: Called then the GLAutoDrawable was
* reshaped
*/
public void reshape(
GLAutoDrawable drawable, int x, int y, int width, int height)
{
this.width = width;
this.height = height;
initPBO(drawable.getGL());
setupView(drawable);
}
/**
* Implementation of GLEventListener - not used
*/
public void displayChanged(
GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged)
{}
/**
* Stops the animator and calls System.exit() in a new Thread.
* (System.exit() may not be called synchronously inside one
* of the JOGL callbacks)
*/
protected void runExit()
{
new Thread(new Runnable()
{
public void run()
{
animatorL.stop();
if (animatorR != null)
{
animatorR.stop();
}
unload();
System.exit(0);
}
}).start();
}
public void updateRotationX(double increment) {
this.rotationX += increment;
}
public void updateRotationY(double increment) {
this.rotationY += increment;
}
public void updateTranslationX(double increment) {
this.translationX += increment;
}
public void updateTranslationY(double increment) {
this.translationY += increment;
}
public void updateTranslationZ(double increment) {
this.translationZ += increment;
}
public void dispose(GLAutoDrawable arg0) {
// TODO Auto-generated method stub
}
}
/*
* Copyright (C) 2010-2014 Martin Berger
* CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
*/