Force image to be a RGB image (there is no writer to convert ARGB type to Bitmap...
[scilab.git] / scilab / modules / scirenderer / src / org / scilab / forge / scirenderer / implementation / jogl / JoGLCanvas.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009-2011 - DIGITEO - Pierre Lando
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
10  */
11
12 package org.scilab.forge.scirenderer.implementation.jogl;
13
14 import org.scilab.forge.scirenderer.Canvas;
15 import org.scilab.forge.scirenderer.Drawer;
16 import org.scilab.forge.scirenderer.implementation.jogl.buffers.JoGLBuffersManager;
17 import org.scilab.forge.scirenderer.implementation.jogl.picking.JoGLPickingManager;
18 import org.scilab.forge.scirenderer.implementation.jogl.renderer.JoGLRendererManager;
19 import org.scilab.forge.scirenderer.implementation.jogl.texture.JoGLTextureManager;
20 import org.scilab.forge.scirenderer.picking.PickingManager;
21
22 import com.jogamp.opengl.util.awt.AWTGLReadBufferUtil;
23 import com.jogamp.opengl.util.awt.ImageUtil;
24
25 import java.awt.Dimension;
26 import java.awt.image.BufferedImage;
27 import java.lang.reflect.InvocationTargetException;
28 import java.util.concurrent.Semaphore;
29
30 import javax.media.opengl.DebugGL2;
31 import javax.media.opengl.GL2;
32 import javax.media.opengl.GLAutoDrawable;
33 import javax.media.opengl.GLCapabilities;
34 import javax.media.opengl.GLContext;
35 import javax.media.opengl.GLDrawableFactory;
36 import javax.media.opengl.GLEventListener;
37 import javax.media.opengl.GLException;
38 import javax.media.opengl.GLOffscreenAutoDrawable;
39 import javax.media.opengl.GLProfile;
40 import javax.swing.SwingUtilities;
41
42 /**
43  * JoGL implementation of a Canvas.
44  *
45  * @author Pierre Lando
46  */
47 public final class JoGLCanvas implements Canvas, GLEventListener {
48
49     private static final double[][][] ANTI_ALIASING_JITTER = {
50         {{0.0, 0.0}},
51         {{0.5, 0.5}, {0.5, -0.5}},
52         {
53             { -0.25, -0.5}, {0.25, 0.5}, {0.75, -0.5}, {0.25, 0.5}
54         },
55         {
56             {0.125, -0.125}, { -0.875, 0.875}, { -0.375, 0.375}, {0.375, 0.625},
57             {0.625, -0.625}, {0.875, 0.125}, { -0.125, -0.875}, { -0.625, -0.375}
58         },
59         {
60             { -0.25, -0.125}, {0.25, -0.875}, {0.75, -0.625}, { -0.75, -0.875},
61             { -0.25, 0.375}, {0.75, -0.125}, {0.25, 0.125}, { -0.25, 0.875},
62             {0.25, -0.375}, { -0.75, 0.125}, { -0.75, 0.625}, { -0.25, -0.625},
63             {0.75, 0.875}, {0.75, 0.375}, { -0.75, -0.375}, {0.25, 0.625}
64         }
65     };
66
67     private final GLAutoDrawable autoDrawable;
68
69     private final JoGLDrawingTools drawingTools;
70     private final JoGLParameters parameters;
71     private final JoGLBuffersManager buffersManager;
72     private final JoGLRendererManager rendererManager;
73     private final JoGLPickingManager pickingManager;
74     private final JoGLTextureManager textureManager;
75
76     private final CanvasAnimator canvasAnimator;
77     private boolean isOffscreen;
78     private DebugGL2 debug;
79     private boolean isValid = true;
80     private boolean displayFinished;
81
82
83     /** The current mainDrawer. */
84     private Drawer mainDrawer;
85
86     /** The anti-aliasing level */
87     private int antiAliasingLevel = 0;
88
89     /**
90      * Default constructor.
91      * @param autoDrawable the JoGL autoDrawable this canvas depend on.
92      */
93     JoGLCanvas(GLAutoDrawable autoDrawable) {
94         this.autoDrawable = autoDrawable;
95         parameters = new JoGLParameters();
96         buffersManager = new JoGLBuffersManager();
97         rendererManager = new JoGLRendererManager();
98         drawingTools = new JoGLDrawingTools(this);
99         pickingManager = new JoGLPickingManager(this);
100         textureManager = new JoGLTextureManager(this);
101
102         autoDrawable.addGLEventListener(this);
103         canvasAnimator = new CanvasAnimator(autoDrawable);
104         canvasAnimator.redraw();
105     }
106
107     /**
108      * Constructor for offscreen rendering
109      * @param width the width
110      * @param height the height
111      */
112     JoGLCanvas(int width, int height) {
113         this(getOffscreenDrawable(width, height));
114         isOffscreen = true;
115     }
116
117     public void setDebugMode(boolean debug) {
118         if (debug) {
119             this.debug = new DebugGL2(autoDrawable.getGL().getGL2());
120         } else {
121             this.debug = null;
122         }
123     }
124
125     // Implementation of getter & setter from Canvas.
126
127     @Override
128     public void setMainDrawer(Drawer mainDrawer) {
129         this.mainDrawer = mainDrawer;
130     }
131
132     @Override
133     public Drawer getMainDrawer() {
134         return mainDrawer;
135     }
136
137     @Override
138     public JoGLRendererManager getRendererManager() {
139         return rendererManager;
140     }
141
142     @Override
143     public JoGLBuffersManager getBuffersManager() {
144         return buffersManager;
145     }
146
147     @Override
148     public PickingManager getPickingManager() {
149         return pickingManager;
150     }
151
152     @Override
153     public JoGLTextureManager getTextureManager() {
154         return textureManager;
155     }
156
157     @Override
158     public int getWidth() {
159         return autoDrawable.getSurfaceWidth();
160     }
161
162     @Override
163     public int getHeight() {
164         return autoDrawable.getSurfaceHeight();
165     }
166
167     @Override
168     public Dimension getDimension() {
169         return new Dimension(autoDrawable.getSurfaceWidth(), autoDrawable.getSurfaceHeight());
170     }
171
172     @Override
173     public void redraw() {
174         canvasAnimator.redraw();
175     }
176
177     @Override
178     public void redrawAndWait() {
179         if (SwingUtilities.isEventDispatchThread()) {
180             if (autoDrawable != null) {
181                 autoDrawable.display();
182             }
183             return;
184         }
185         try {
186             SwingUtilities.invokeAndWait(new Runnable() {
187                 public void run() {
188                     autoDrawable.display();
189                 }
190             });
191         } catch (Exception e) { }
192     }
193
194     @Override
195     public void waitImage() {
196         canvasAnimator.waitEndOfDrawing();
197     }
198
199     @Override
200     public int getAntiAliasingLevel() {
201         return antiAliasingLevel;
202     }
203
204     @Override
205     public void setAntiAliasingLevel(int antiAliasingLevel) {
206         this.antiAliasingLevel = antiAliasingLevel;
207     }
208
209     // JoGLCanvas specific getter.
210
211     /**
212      * Return the OpenGl context.
213      * @return the OpenGl context.
214      */
215     public GL2 getGl() {
216         if (debug == null) {
217             return autoDrawable.getGL().getGL2();
218         } else {
219             return debug;
220         }
221     }
222
223     /**
224      * Return the rendering parameters.
225      * @return the rendering parameters.
226      */
227     public JoGLParameters getJoGLParameters() {
228         return parameters;
229     }
230
231     /**
232      * Get an image from the autoDrawable
233      * @return an image
234      */
235     public BufferedImage getImage() {
236         return getImage(true);
237     }
238
239     /**
240      * Get an image from the autoDrawable
241      * @return an image
242      */
243     public BufferedImage getImage(final boolean alpha) {
244         while (!canvasAnimator.isDrawFinished() || !displayFinished) {
245             try {
246                 Thread.sleep(10);
247             } catch (InterruptedException e) {
248                 break;
249             }
250         }
251
252         final BufferedImage[] image = new BufferedImage[1];
253         final GLContext context = autoDrawable.getContext();
254
255         if (SwingUtilities.isEventDispatchThread()) {
256             context.makeCurrent();
257             AWTGLReadBufferUtil buffer = new AWTGLReadBufferUtil(GLProfile.getDefault(), alpha);
258             image[0] = buffer.readPixelsToBufferedImage(getGl(), 0, 0, autoDrawable.getSurfaceWidth(), autoDrawable.getSurfaceHeight(), true);
259             context.release();
260         } else {
261             try {
262                 SwingUtilities.invokeAndWait(new Runnable() {
263                     public void run() {
264                         context.makeCurrent();
265                         AWTGLReadBufferUtil buffer = new AWTGLReadBufferUtil(GLProfile.getDefault(), alpha);
266                         image[0] = buffer.readPixelsToBufferedImage(getGl(), 0, 0, autoDrawable.getSurfaceWidth(), autoDrawable.getSurfaceHeight(), true);
267                         context.release();
268                     }
269                 });
270             } catch (InterruptedException e) {
271
272             } catch (InvocationTargetException e) {
273                 System.err.println(e);
274                 e.printStackTrace();
275             }
276         }
277
278         return image[0];
279     }
280
281     /**
282      * Destroy the GLPbuffer
283      */
284     public void destroy() {
285         if (isOffscreen) {
286             ((GLOffscreenAutoDrawable) autoDrawable).destroy();
287         }
288         try {
289             isValid = false;
290             canvasAnimator.finalize();//Thread.dumpStack();
291         } catch (Throwable e) {
292             // TODO: handle exception
293         }
294     }
295
296     /**
297      * Creates a GLPbuffer for an offscreen rendering
298      * @param width the width
299      * @param height the height
300      * @return a GLPbuffer
301      */
302     private static GLAutoDrawable getOffscreenDrawable(int width, int height) {
303         GLDrawableFactory factory = GLDrawableFactory.getDesktopFactory();
304
305         GLCapabilities capabilities = new GLCapabilities(GLProfile.getDefault());
306         capabilities.setPBuffer(true);
307
308         return factory.createOffscreenAutoDrawable(null, capabilities, null, width, height);
309     }
310
311     // Implementation of function from GLEventListener.
312     @Override
313     public void display(GLAutoDrawable glAutoDrawable) {
314         if (isValid) {
315             displayFinished = false;
316             GL2 gl = getGl().getGL2();
317             buffersManager.glSynchronize(gl);
318             rendererManager.glSynchronize(gl);
319             drawingTools.glSynchronize(gl);
320
321             if (mainDrawer != null) {
322                 gl.glEnable(GL2.GL_DEPTH_TEST);
323                 gl.glDepthFunc(GL2.GL_LEQUAL); // Set to less equal to allow last drawn object to be on the top.
324
325                 if ((antiAliasingLevel > 0) && (antiAliasingLevel < 5) && (drawingTools.getGLCapacity().isAccumulationBufferPresent())) {
326                     org.scilab.forge.scirenderer.renderer.Renderer renderer = rendererManager.createRenderer();
327                     renderer.setDrawer(mainDrawer);
328
329                     double[][] jitter = ANTI_ALIASING_JITTER[antiAliasingLevel];
330                     Dimension dimension = getDimension();
331                     gl.glClear(GL2.GL_ACCUM_BUFFER_BIT);
332                     for (double[] aJitter : jitter) {
333                         drawingTools.glSynchronize(gl);
334
335                         gl.glMatrixMode(GL2.GL_PROJECTION);
336                         gl.glLoadIdentity();
337                         gl.glTranslated(aJitter[0] / dimension.getWidth(), aJitter[1] / dimension.getHeight(), 0.);
338
339                         gl.glClear(GL2.GL_DEPTH_BUFFER_BIT);
340                         rendererManager.draw(drawingTools, renderer);
341                         //mainDrawer.draw(drawingTools);
342                         gl.glReadBuffer(GL2.GL_BACK);
343                         gl.glAccum(GL2.GL_ACCUM, 1f / jitter.length);
344                     }
345
346                     rendererManager.dispose(drawingTools, renderer);
347
348                     gl.glDrawBuffer(GL2.GL_BACK);
349                     gl.glAccum(GL2.GL_RETURN, 1.0f);
350                 } else {
351                     gl.glMatrixMode(GL2.GL_PROJECTION);
352                     gl.glLoadIdentity();
353                     gl.glClear(GL2.GL_DEPTH_BUFFER_BIT);
354                     mainDrawer.draw(drawingTools);
355                 }
356             }
357
358             pickingManager.glConsume(drawingTools);
359             displayFinished = true;
360         }
361     }
362
363     @Override
364     public void init(GLAutoDrawable glAutoDrawable) {
365         textureManager.glReload();
366         buffersManager.glReload();
367         rendererManager.glReload();
368     }
369
370     @Override
371     public void reshape(GLAutoDrawable glAutoDrawable, int x, int y, int width, int height) {
372     }
373
374     @Override
375     public void dispose(GLAutoDrawable drawable) { }
376
377     /**
378      * this class manage asynchronous scene drawing.
379      */
380     private class CanvasAnimator implements Runnable {
381
382         private final Semaphore semaphore = new Semaphore(1);
383         private final Thread thread;
384         private final GLAutoDrawable autoDrawable;
385         private boolean running = true;
386
387         public CanvasAnimator(GLAutoDrawable autoDrawable) {
388             this.autoDrawable = autoDrawable;
389             this.thread = new Thread(this);
390             thread.start();
391             //System.err.println("[DEBUG] nb threads = "+Thread.activeCount());
392         }
393
394         @Override
395         public void finalize() throws Throwable {
396             running = false;
397             // we increment the semaphore to allow run() to unlock it and to be sure
398             // to go out.
399             semaphore.release();
400             autoDrawable.destroy();
401             super.finalize();
402         }
403
404         public synchronized boolean isDrawFinished() {
405             return semaphore.availablePermits() == 0;
406         }
407
408         /** Ask the animator to perform a draw later. */
409         public synchronized void redraw() {
410             semaphore.release();
411         }
412
413         /** Wait until a drawing has been performed */
414         public void waitEndOfDrawing() {
415             semaphore.drainPermits();
416         }
417
418         @Override
419         public void run() {
420             while (running) {
421                 try {
422                     semaphore.acquire();
423                     semaphore.drainPermits();
424                     if (running) {
425                         autoDrawable.display();
426                     }
427                 } catch (InterruptedException e) {
428                     if (running) {
429                         Thread.currentThread().interrupt();
430                     }
431                     break;
432                 } catch (GLException e) {
433                     if (running) {
434                         throw e;
435                     }
436                     break;
437                 }
438             }
439         }
440     }
441 }