Export graphics: fix flip / mirror after 8565ff28
[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         while (!canvasAnimator.isDrawFinished() || !displayFinished) {
237             try {
238                 Thread.sleep(10);
239             } catch (InterruptedException e) {
240                 break;
241             }
242         }
243
244         final BufferedImage[] image = new BufferedImage[1];
245         final GLContext context = autoDrawable.getContext();
246
247         if (SwingUtilities.isEventDispatchThread()) {
248             context.makeCurrent();
249             AWTGLReadBufferUtil buffer = new AWTGLReadBufferUtil(GLProfile.getDefault(), true);
250             image[0] = buffer.readPixelsToBufferedImage(getGl(), 0, 0, autoDrawable.getSurfaceWidth(), autoDrawable.getSurfaceHeight(), true);
251             context.release();
252         } else {
253             try {
254                 SwingUtilities.invokeAndWait(new Runnable() {
255                     public void run() {
256                         context.makeCurrent();
257                         AWTGLReadBufferUtil buffer = new AWTGLReadBufferUtil(GLProfile.getDefault(), true);
258                         image[0] = buffer.readPixelsToBufferedImage(getGl(), 0, 0, autoDrawable.getSurfaceWidth(), autoDrawable.getSurfaceHeight(), true);
259                         context.release();
260                     }
261                 });
262             } catch (InterruptedException e) {
263
264             } catch (InvocationTargetException e) {
265                 System.err.println(e);
266                 e.printStackTrace();
267             }
268         }
269
270         return image[0];
271     }
272
273     /**
274      * Destroy the GLPbuffer
275      */
276     public void destroy() {
277         if (isOffscreen) {
278             ((GLOffscreenAutoDrawable) autoDrawable).destroy();
279         }
280         try {
281             isValid = false;
282             canvasAnimator.finalize();//Thread.dumpStack();
283         } catch (Throwable e) {
284             // TODO: handle exception
285         }
286     }
287
288     /**
289      * Creates a GLPbuffer for an offscreen rendering
290      * @param width the width
291      * @param height the height
292      * @return a GLPbuffer
293      */
294     private static GLAutoDrawable getOffscreenDrawable(int width, int height) {
295         GLDrawableFactory factory = GLDrawableFactory.getDesktopFactory();
296
297         GLCapabilities capabilities = new GLCapabilities(GLProfile.getDefault());
298         capabilities.setPBuffer(true);
299
300         return factory.createOffscreenAutoDrawable(null, capabilities, null, width, height);
301     }
302
303     // Implementation of function from GLEventListener.
304     @Override
305     public void display(GLAutoDrawable glAutoDrawable) {
306         if (isValid) {
307             displayFinished = false;
308             GL2 gl = getGl().getGL2();
309             buffersManager.glSynchronize(gl);
310             rendererManager.glSynchronize(gl);
311             drawingTools.glSynchronize(gl);
312
313             if (mainDrawer != null) {
314                 gl.glEnable(GL2.GL_DEPTH_TEST);
315                 gl.glDepthFunc(GL2.GL_LEQUAL); // Set to less equal to allow last drawn object to be on the top.
316
317                 if ((antiAliasingLevel > 0) && (antiAliasingLevel < 5) && (drawingTools.getGLCapacity().isAccumulationBufferPresent())) {
318                     org.scilab.forge.scirenderer.renderer.Renderer renderer = rendererManager.createRenderer();
319                     renderer.setDrawer(mainDrawer);
320
321                     double[][] jitter = ANTI_ALIASING_JITTER[antiAliasingLevel];
322                     Dimension dimension = getDimension();
323                     gl.glClear(GL2.GL_ACCUM_BUFFER_BIT);
324                     for (double[] aJitter : jitter) {
325                         drawingTools.glSynchronize(gl);
326
327                         gl.glMatrixMode(GL2.GL_PROJECTION);
328                         gl.glLoadIdentity();
329                         gl.glTranslated(aJitter[0] / dimension.getWidth(), aJitter[1] / dimension.getHeight(), 0.);
330
331                         gl.glClear(GL2.GL_DEPTH_BUFFER_BIT);
332                         rendererManager.draw(drawingTools, renderer);
333                         //mainDrawer.draw(drawingTools);
334                         gl.glReadBuffer(GL2.GL_BACK);
335                         gl.glAccum(GL2.GL_ACCUM, 1f / jitter.length);
336                     }
337
338                     rendererManager.dispose(drawingTools, renderer);
339
340                     gl.glDrawBuffer(GL2.GL_BACK);
341                     gl.glAccum(GL2.GL_RETURN, 1.0f);
342                 } else {
343                     gl.glMatrixMode(GL2.GL_PROJECTION);
344                     gl.glLoadIdentity();
345                     gl.glClear(GL2.GL_DEPTH_BUFFER_BIT);
346                     mainDrawer.draw(drawingTools);
347                 }
348             }
349
350             pickingManager.glConsume(drawingTools);
351             displayFinished = true;
352         }
353     }
354
355     @Override
356     public void init(GLAutoDrawable glAutoDrawable) {
357         textureManager.glReload();
358         buffersManager.glReload();
359         rendererManager.glReload();
360     }
361
362     @Override
363     public void reshape(GLAutoDrawable glAutoDrawable, int x, int y, int width, int height) {
364     }
365
366     @Override
367     public void dispose(GLAutoDrawable drawable) { }
368
369     /**
370      * this class manage asynchronous scene drawing.
371      */
372     private class CanvasAnimator implements Runnable {
373
374         private final Semaphore semaphore = new Semaphore(1);
375         private final Thread thread;
376         private final GLAutoDrawable autoDrawable;
377         private boolean running = true;
378
379         public CanvasAnimator(GLAutoDrawable autoDrawable) {
380             this.autoDrawable = autoDrawable;
381             this.thread = new Thread(this);
382             thread.start();
383             //System.err.println("[DEBUG] nb threads = "+Thread.activeCount());
384         }
385
386         @Override
387         public void finalize() throws Throwable {
388             running = false;
389             // we increment the semaphore to allow run() to unlock it and to be sure
390             // to go out.
391             semaphore.release();
392             autoDrawable.destroy();
393             super.finalize();
394         }
395
396         public synchronized boolean isDrawFinished() {
397             return semaphore.availablePermits() == 0;
398         }
399
400         /** Ask the animator to perform a draw later. */
401         public synchronized void redraw() {
402             semaphore.release();
403         }
404
405         /** Wait until a drawing has been performed */
406         public void waitEndOfDrawing() {
407             semaphore.drainPermits();
408         }
409
410         @Override
411         public void run() {
412             while (running) {
413                 try {
414                     semaphore.acquire();
415                     semaphore.drainPermits();
416                     if (running) {
417                         autoDrawable.display();
418                     }
419                 } catch (InterruptedException e) {
420                     if (running) {
421                         Thread.currentThread().interrupt();
422                     }
423                     break;
424                 } catch (GLException e) {
425                     if (running) {
426                         throw e;
427                     }
428                     break;
429                 }
430             }
431         }
432     }
433 }