From 32f216838432d0a67ce78061aa8a09261e3c3716 Mon Sep 17 00:00:00 2001
From: Sven Gothel
+
+ The GL2JPanel can be made transparent by creating it with a
+ GLCapabilities object with alpha bits specified and calling {@link
+ #setOpaque}(false). Pixels with resulting OpenGL alpha values less
+ than 1.0 will be overlaid on any underlying Swing rendering.
+
+ Notes specific to the Reference Implementation: This component
+ attempts to use hardware-accelerated rendering via pbuffers and
+ falls back on to software rendering if problems occur.
+ Note that because this component attempts to use pbuffers for
+ rendering, and because pbuffers can not be resized, somewhat
+ surprising behavior may occur during resize operations; the {@link
+ GLEventListener#init} method may be called multiple times as the
+ pbuffer is resized to be able to cover the size of the GL2JPanel.
+ This behavior is correct, as the textures and display lists for
+ the GL2JPanel will have been lost during the resize operation. The
+ application should attempt to make its GLEventListener.init()
+ methods as side-effect-free as possible.
+
+*/
+
+public class GL2JPanel extends JPanel implements AWTGLAutoDrawable {
+ private static final boolean DEBUG = Debug.debug("GL2JPanel");
+ private static final boolean VERBOSE = Debug.verbose();
+
+ private GLDrawableHelper drawableHelper = new GLDrawableHelper();
+ private volatile boolean isInitialized;
+ private volatile boolean shouldInitialize = false;
+
+ // Data used for either pbuffers or pixmap-based offscreen surfaces
+ private GLCapabilities offscreenCaps;
+ private GLCapabilitiesChooser chooser;
+ private GLContext shareWith;
+ // This image is exactly the correct size to render into the panel
+ private BufferedImage offscreenImage;
+ // One of these is used to store the read back pixels before storing
+ // in the BufferedImage
+ private ByteBuffer readBackBytes;
+ private IntBuffer readBackInts;
+ private int readBackWidthInPixels;
+ private int readBackHeightInPixels;
+ // Width of the actual GL2JPanel
+ private int panelWidth = 0;
+ private int panelHeight = 0;
+ private Updater updater;
+ private int awtFormat;
+ private int glFormat;
+ private int glType;
+ // Lazy reshape notification
+ private boolean handleReshape = false;
+ private boolean sendReshape = true;
+
+ private static GLDrawableFactoryImpl factory;
+
+ // Implementation using pbuffers
+ private static boolean hardwareAccelerationDisabled =
+ Debug.isPropertyDefined("jogl.gljpanel.nohw");
+ private static boolean softwareRenderingDisabled =
+ Debug.isPropertyDefined("jogl.gljpanel.nosw");
+ private GLPbuffer pbuffer;
+ private int pbufferWidth = 256;
+ private int pbufferHeight = 256;
+
+ // Implementation using software rendering
+ private GLDrawableImpl offscreenDrawable;
+ private GLContextImpl offscreenContext;
+
+ // For handling reshape events lazily
+ private int reshapeX;
+ private int reshapeY;
+ private int reshapeWidth;
+ private int reshapeHeight;
+
+ // For saving/restoring of OpenGL state during ReadPixels
+ private int[] swapbytes = new int[1];
+ private int[] rowlength = new int[1];
+ private int[] skiprows = new int[1];
+ private int[] skippixels = new int[1];
+ private int[] alignment = new int[1];
+
+ // Implementation using Java2D OpenGL pipeline's back buffer
+ private boolean oglPipelineEnabled =
+ Java2D.isOGLPipelineActive() &&
+ !Debug.isPropertyDefined("jogl.gljpanel.noogl");
+ // Opaque Object identifier representing the Java2D surface we are
+ // drawing to; used to determine when to destroy and recreate JOGL
+ // context
+ private Object j2dSurface;
+ // Graphics object being used during Java2D update action
+ // (absolutely essential to cache this)
+ private Graphics cached2DGraphics;
+ // No-op context representing the Java2D OpenGL context
+ private GLContext j2dContext;
+ // Context associated with no-op drawable representing the JOGL
+ // OpenGL context
+ private GLDrawable joglDrawable;
+ // The real OpenGL context JOGL uses to render
+ private GLContext joglContext;
+ // State captured from Java2D OpenGL context necessary in order to
+ // properly render into Java2D back buffer
+ private int[] drawBuffer = new int[1];
+ private int[] readBuffer = new int[1];
+ // This is required when the FBO option of the Java2D / OpenGL
+ // pipeline is active
+ private int[] frameBuffer = new int[1];
+ // Current (as of this writing) NVidia drivers have a couple of bugs
+ // relating to the sharing of framebuffer and renderbuffer objects
+ // between contexts. It appears we have to (a) reattach the color
+ // attachment and (b) actually create new depth buffer storage and
+ // attach it in order for the FBO to behave properly in our context.
+ private boolean checkedForFBObjectWorkarounds;
+ private boolean fbObjectWorkarounds;
+ private int[] frameBufferDepthBuffer;
+ private int[] frameBufferTexture;
+ private boolean createNewDepthBuffer;
+ // Current (as of this writing) ATI drivers have problems when the
+ // same FBO is bound in two different contexts. Here we check for
+ // this case and explicitly release the FBO from Java2D's context
+ // before switching to ours. Java2D will re-bind the FBO when it
+ // makes its context current the next time. Interestingly, if we run
+ // this code path on NVidia hardware, it breaks the rendering
+ // results -- no output is generated. This doesn't appear to be an
+ // interaction with the abovementioned NVidia-specific workarounds,
+ // as even if we disable that code the FBO is still reported as
+ // incomplete in our context.
+ private boolean checkedGLVendor;
+ private boolean vendorIsATI;
+
+ // Holding on to this GraphicsConfiguration is a workaround for a
+ // problem in the Java 2D / JOGL bridge when FBOs are enabled; see
+ // comment related to Issue 274 below
+ private GraphicsConfiguration workaroundConfig;
+
+ // These are always set to (0, 0) except when the Java2D / OpenGL
+ // pipeline is active
+ private int viewportX;
+ private int viewportY;
+
+ static {
+ // Force eager initialization of part of the Java2D class since
+ // otherwise it's likely it will try to be initialized while on
+ // the Queue Flusher Thread, which is not allowed
+ if (Java2D.isOGLPipelineActive() && Java2D.isFBOEnabled()) {
+ Java2D.getShareContext(GraphicsEnvironment.
+ getLocalGraphicsEnvironment().
+ getDefaultScreenDevice().
+ getDefaultConfiguration());
+ }
+ GLProfile.setProfile(GLProfile.GL2);
+ factory = GLDrawableFactoryImpl.getFactoryImpl(true);
+ }
+
+ /** Creates a new GL2JPanel component with a default set of OpenGL
+ capabilities and using the default OpenGL capabilities selection
+ mechanism. */
+ public GL2JPanel() {
+ this(null);
+ }
+
+ /** Creates a new GL2JPanel component with the requested set of
+ OpenGL capabilities, using the default OpenGL capabilities
+ selection mechanism. */
+ public GL2JPanel(GLCapabilities capabilities) {
+ this(capabilities, null, null);
+ }
+
+ /** Creates a new GL2JPanel component. The passed GLCapabilities
+ specifies the OpenGL capabilities for the component; if null, a
+ default set of capabilities is used. The GLCapabilitiesChooser
+ specifies the algorithm for selecting one of the available
+ GLCapabilities for the component; a DefaultGLCapabilitesChooser
+ is used if null is passed for this argument. The passed
+ GLContext specifies an OpenGL context with which to share
+ textures, display lists and other OpenGL state, and may be null
+ if sharing is not desired. See the note in the overview documentation on
+ context sharing.
+ */
+ public GL2JPanel(GLCapabilities capabilities, GLCapabilitiesChooser chooser, GLContext shareWith) {
+ super();
+
+ // Works around problems on many vendors' cards; we don't need a
+ // back buffer for the offscreen surface anyway
+ if (capabilities != null) {
+ offscreenCaps = (GLCapabilities) capabilities.clone();
+ } else {
+ offscreenCaps = new GLCapabilities();
+ }
+ offscreenCaps.setDoubleBuffered(false);
+ this.chooser = ((chooser != null) ? chooser : new DefaultGLCapabilitiesChooser());
+ this.shareWith = shareWith;
+ }
+
+ public void display() {
+ if (EventQueue.isDispatchThread()) {
+ // Want display() to be synchronous, so call paintImmediately()
+ paintImmediately(0, 0, getWidth(), getHeight());
+ } else {
+ // Multithreaded redrawing of Swing components is not allowed,
+ // so do everything on the event dispatch thread
+ try {
+ EventQueue.invokeAndWait(paintImmediatelyAction);
+ } catch (Exception e) {
+ throw new GLException(e);
+ }
+ }
+ }
+
+ private void captureJ2DState(GL2 gl, Graphics g) {
+ gl.glGetIntegerv(GL2.GL_DRAW_BUFFER, drawBuffer, 0);
+ gl.glGetIntegerv(GL2.GL_READ_BUFFER, readBuffer, 0);
+ if (Java2D.isFBOEnabled() &&
+ Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
+ if (DEBUG && VERBOSE) {
+ System.err.println("GL2JPanel: Fetching GL_FRAMEBUFFER_BINDING_EXT");
+ }
+ gl.glGetIntegerv(GL2.GL_FRAMEBUFFER_BINDING_EXT, frameBuffer, 0);
+
+ if (fbObjectWorkarounds ||
+ !checkedForFBObjectWorkarounds) {
+ // See above for description of what we are doing here
+ if (frameBufferTexture == null)
+ frameBufferTexture = new int[1];
+
+ // Query the framebuffer for its color buffer so we can hook
+ // it back up in our context (should not be necessary)
+ gl.glGetFramebufferAttachmentParameteriv(GL2.GL_FRAMEBUFFER_EXT,
+ GL2.GL_COLOR_ATTACHMENT0_EXT,
+ GL2.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT,
+ frameBufferTexture, 0);
+ if (DEBUG && VERBOSE) {
+ System.err.println("GL2JPanel: FBO COLOR_ATTACHMENT0: " + frameBufferTexture[0]);
+ }
+ }
+
+ if (!checkedGLVendor) {
+ checkedGLVendor = true;
+ String vendor = gl.glGetString(GL2.GL_VENDOR);
+
+ if ((vendor != null) &&
+ vendor.startsWith("ATI")) {
+ vendorIsATI = true;
+ }
+ }
+
+ if (vendorIsATI) {
+ // Unbind the FBO from Java2D's context as it appears that
+ // driver bugs on ATI's side are causing problems if the FBO is
+ // simultaneously bound to more than one context. Java2D will
+ // re-bind the FBO during the next validation of its context.
+ // Note: this breaks rendering at least on NVidia hardware
+ gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER_EXT, 0);
+ }
+ }
+ }
+
+ private boolean preGL(Graphics g) {
+ GL2 gl = joglContext.getGL().getGL2();
+ // Set up needed state in JOGL context from Java2D context
+ gl.glEnable(GL2.GL_SCISSOR_TEST);
+ Rectangle r = Java2D.getOGLScissorBox(g);
+
+ if (r == null) {
+ if (DEBUG && VERBOSE) {
+ System.err.println("Java2D.getOGLScissorBox() returned null");
+ }
+ return false;
+ }
+ if (DEBUG && VERBOSE) {
+ System.err.println("GL2JPanel: gl.glScissor(" + r.x + ", " + r.y + ", " + r.width + ", " + r.height + ")");
+ }
+
+ gl.glScissor(r.x, r.y, r.width, r.height);
+ Rectangle oglViewport = Java2D.getOGLViewport(g, panelWidth, panelHeight);
+ // If the viewport X or Y changes, in addition to the panel's
+ // width or height, we need to send a reshape operation to the
+ // client
+ if ((viewportX != oglViewport.x) ||
+ (viewportY != oglViewport.y)) {
+ sendReshape = true;
+ if (DEBUG) {
+ System.err.println("Sending reshape because viewport changed");
+ System.err.println(" viewportX (" + viewportX + ") ?= oglViewport.x (" + oglViewport.x + ")");
+ System.err.println(" viewportY (" + viewportY + ") ?= oglViewport.y (" + oglViewport.y + ")");
+ }
+ }
+ viewportX = oglViewport.x;
+ viewportY = oglViewport.y;
+
+ // If the FBO option is active, bind to the FBO from the Java2D
+ // context.
+ // Note that all of the plumbing in the context sharing stuff will
+ // allow us to bind to this object since it's in our namespace.
+ if (Java2D.isFBOEnabled() &&
+ Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
+ if (DEBUG && VERBOSE) {
+ System.err.println("GL2JPanel: Binding to framebuffer object " + frameBuffer[0]);
+ }
+
+ // The texture target for Java2D's OpenGL pipeline when using FBOs
+ // -- either GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE_ARB
+ int fboTextureTarget = Java2D.getOGLTextureType(g);
+
+ if (!checkedForFBObjectWorkarounds) {
+ checkedForFBObjectWorkarounds = true;
+ gl.glBindTexture(fboTextureTarget, 0);
+ gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER_EXT, frameBuffer[0]);
+ if (gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER_EXT) !=
+ GL2.GL_FRAMEBUFFER_COMPLETE_EXT) {
+ // Need to do workarounds
+ fbObjectWorkarounds = true;
+ createNewDepthBuffer = true;
+ if (DEBUG) {
+ System.err.println("-- GL2JPanel: discovered frame_buffer_object workarounds to be necessary");
+ }
+ } else {
+ // Don't need the frameBufferTexture temporary any more
+ frameBufferTexture = null;
+ }
+ }
+
+ if (fbObjectWorkarounds && createNewDepthBuffer) {
+ if (frameBufferDepthBuffer == null)
+ frameBufferDepthBuffer = new int[1];
+
+ // Create our own depth renderbuffer and associated storage
+ // If we have an old one, delete it
+ if (frameBufferDepthBuffer[0] != 0) {
+ gl.glDeleteRenderbuffers(1, frameBufferDepthBuffer, 0);
+ frameBufferDepthBuffer[0] = 0;
+ }
+
+ gl.glBindTexture(fboTextureTarget, frameBufferTexture[0]);
+ int[] width = new int[1];
+ int[] height = new int[1];
+ gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2.GL_TEXTURE_WIDTH, width, 0);
+ gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2.GL_TEXTURE_HEIGHT, height, 0);
+
+ gl.glGenRenderbuffers(1, frameBufferDepthBuffer, 0);
+ if (DEBUG) {
+ System.err.println("GL2JPanel: Generated frameBufferDepthBuffer " + frameBufferDepthBuffer[0] +
+ " with width " + width[0] + ", height " + height[0]);
+ }
+
+ gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER_EXT, frameBufferDepthBuffer[0]);
+ // FIXME: may need a loop here like in Java2D
+ gl.glRenderbufferStorage(GL2.GL_RENDERBUFFER_EXT, GL2.GL_DEPTH_COMPONENT24, width[0], height[0]);
+
+ gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER_EXT, 0);
+ createNewDepthBuffer = false;
+ }
+
+ gl.glBindTexture(fboTextureTarget, 0);
+ gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER_EXT, frameBuffer[0]);
+
+ if (fbObjectWorkarounds) {
+ // Hook up the color and depth buffer attachment points for this framebuffer
+ gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER_EXT,
+ GL2.GL_COLOR_ATTACHMENT0_EXT,
+ fboTextureTarget,
+ frameBufferTexture[0],
+ 0);
+ if (DEBUG && VERBOSE) {
+ System.err.println("GL2JPanel: frameBufferDepthBuffer: " + frameBufferDepthBuffer[0]);
+ }
+ gl.glFramebufferRenderbuffer(GL2.GL_FRAMEBUFFER_EXT,
+ GL2.GL_DEPTH_ATTACHMENT_EXT,
+ GL2.GL_RENDERBUFFER_EXT,
+ frameBufferDepthBuffer[0]);
+ }
+
+ if (DEBUG) {
+ int status = gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER_EXT);
+ if (status != GL2.GL_FRAMEBUFFER_COMPLETE_EXT) {
+ throw new GLException("Error: framebuffer was incomplete: status = 0x" +
+ Integer.toHexString(status));
+ }
+ }
+ } else {
+ if (DEBUG && VERBOSE) {
+ System.err.println("GL2JPanel: Setting up drawBuffer " + drawBuffer[0] +
+ " and readBuffer " + readBuffer[0]);
+ }
+
+ gl.glDrawBuffer(drawBuffer[0]);
+ gl.glReadBuffer(readBuffer[0]);
+ }
+
+ return true;
+ }
+
+ private void postGL(Graphics g) {
+ if (Java2D.isFBOEnabled() &&
+ Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
+ // Unbind the framebuffer from our context to work around
+ // apparent driver bugs or at least unspecified behavior causing
+ // OpenGL to run out of memory with certain cards and drivers
+ GL2 gl = joglContext.getGL().getGL2();
+ gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER_EXT, 0);
+ }
+ }
+
+ /** Overridden to cause OpenGL rendering to be performed during
+ repaint cycles. Subclasses which override this method must call
+ super.paintComponent() in their paintComponent() method in order
+ to function properly.
+
+ Overrides:
+
+
+ Overrides:
+
+
+ Overrides:
+
+
+ Overrides:
+
*/
+ protected void paintComponent(final Graphics g) {
+ if (Beans.isDesignTime()) {
+ // Make GL2JPanel behave better in NetBeans GUI builder
+ g.setColor(Color.BLACK);
+ g.fillRect(0, 0, getWidth(), getHeight());
+ FontMetrics fm = g.getFontMetrics();
+ String name = getName();
+ if (name == null) {
+ name = getClass().getName();
+ int idx = name.lastIndexOf('.');
+ if (idx >= 0) {
+ name = name.substring(idx + 1);
+ }
+ }
+ Rectangle2D bounds = fm.getStringBounds(name, g);
+ g.setColor(Color.WHITE);
+ g.drawString(name,
+ (int) ((getWidth() - bounds.getWidth()) / 2),
+ (int) ((getHeight() + bounds.getHeight()) / 2));
+ return;
+ }
+
+ if (shouldInitialize) {
+ initialize();
+ }
+
+ if (!isInitialized) {
+ return;
+ }
+
+ // NOTE: must do this when the context is not current as it may
+ // involve destroying the pbuffer (current context) and
+ // re-creating it -- tricky to do properly while the context is
+ // current
+ if (handleReshape) {
+ handleReshape();
+ }
+
+ updater.setGraphics(g);
+
+ if (oglPipelineEnabled) {
+
+ // This is a workaround for an issue in the Java 2D / JOGL
+ // bridge (reported by an end user as JOGL Issue 274) where Java
+ // 2D can occasionally leave its internal OpenGL context current
+ // to the on-screen window rather than its internal "scratch"
+ // pbuffer surface to which the FBO is attached. JOGL expects to
+ // find a stable OpenGL drawable (on Windows, an HDC) upon which
+ // it can create another OpenGL context. It turns out that, on
+ // Windows, when Java 2D makes its internal OpenGL context
+ // current against the window in order to put pixels on the
+ // screen, it gets the device context for the window, makes its
+ // context current, and releases the device context. This means
+ // that when JOGL's Runnable gets to run below, the HDC is
+ // already invalid. The workaround for this is to force Java 2D
+ // to make its context current to the scratch surface, which we
+ // can do by executing an empty Runnable with the "shared"
+ // context current. This will be fixed in a Java SE 6 update
+ // release, hopefully 6u2.
+ if (Java2D.isFBOEnabled()) {
+ if (workaroundConfig == null) {
+ workaroundConfig = GraphicsEnvironment.
+ getLocalGraphicsEnvironment().
+ getDefaultScreenDevice().
+ getDefaultConfiguration();
+ }
+ Java2D.invokeWithOGLSharedContextCurrent(workaroundConfig, new Runnable() { public void run() {}});
+ }
+
+ Java2D.invokeWithOGLContextCurrent(g, new Runnable() {
+ public void run() {
+ if (DEBUG && VERBOSE) {
+ System.err.println("-- In invokeWithOGLContextCurrent");
+ }
+
+ // Create no-op context representing Java2D context
+ if (j2dContext == null) {
+ j2dContext = factory.createExternalGLContext();
+ if (DEBUG) {
+ j2dContext.setGL(new DebugGL2(j2dContext.getGL().getGL2()));
+ }
+
+ // Check to see whether we can support the requested
+ // capabilities or need to fall back to a pbuffer
+ // FIXME: add more checks?
+
+ j2dContext.makeCurrent();
+ GL2 gl = j2dContext.getGL().getGL2();
+ if ((getGLInteger(gl, GL2.GL_RED_BITS) < offscreenCaps.getRedBits()) ||
+ (getGLInteger(gl, GL2.GL_GREEN_BITS) < offscreenCaps.getGreenBits()) ||
+ (getGLInteger(gl, GL2.GL_BLUE_BITS) < offscreenCaps.getBlueBits()) ||
+ // (getGLInteger(gl, GL2.GL_ALPHA_BITS) < offscreenCaps.getAlphaBits()) ||
+ (getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) < offscreenCaps.getAccumRedBits()) ||
+ (getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) < offscreenCaps.getAccumGreenBits()) ||
+ (getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) < offscreenCaps.getAccumBlueBits()) ||
+ (getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) < offscreenCaps.getAccumAlphaBits()) ||
+ // (getGLInteger(gl, GL2.GL_DEPTH_BITS) < offscreenCaps.getDepthBits()) ||
+ (getGLInteger(gl, GL2.GL_STENCIL_BITS) < offscreenCaps.getStencilBits())) {
+ if (DEBUG) {
+ System.err.println("GL2JPanel: Falling back to pbuffer-based support because Java2D context insufficient");
+ System.err.println(" Available Required");
+ System.err.println("GL_RED_BITS " + getGLInteger(gl, GL2.GL_RED_BITS) + " " + offscreenCaps.getRedBits());
+ System.err.println("GL_GREEN_BITS " + getGLInteger(gl, GL2.GL_GREEN_BITS) + " " + offscreenCaps.getGreenBits());
+ System.err.println("GL_BLUE_BITS " + getGLInteger(gl, GL2.GL_BLUE_BITS) + " " + offscreenCaps.getBlueBits());
+ System.err.println("GL_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ALPHA_BITS) + " " + offscreenCaps.getAlphaBits());
+ System.err.println("GL_ACCUM_RED_BITS " + getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) + " " + offscreenCaps.getAccumRedBits());
+ System.err.println("GL_ACCUM_GREEN_BITS " + getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) + " " + offscreenCaps.getAccumGreenBits());
+ System.err.println("GL_ACCUM_BLUE_BITS " + getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) + " " + offscreenCaps.getAccumBlueBits());
+ System.err.println("GL_ACCUM_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) + " " + offscreenCaps.getAccumAlphaBits());
+ System.err.println("GL_DEPTH_BITS " + getGLInteger(gl, GL2.GL_DEPTH_BITS) + " " + offscreenCaps.getDepthBits());
+ System.err.println("GL_STENCIL_BITS " + getGLInteger(gl, GL2.GL_STENCIL_BITS) + " " + offscreenCaps.getStencilBits());
+ }
+ isInitialized = false;
+ shouldInitialize = true;
+ oglPipelineEnabled = false;
+ handleReshape = true;
+ j2dContext.release();
+ j2dContext.destroy();
+ j2dContext = null;
+ return;
+ }
+ j2dContext.release();
+ }
+
+ j2dContext.makeCurrent();
+ try {
+ captureJ2DState(j2dContext.getGL().getGL2(), g);
+ Object curSurface = Java2D.getOGLSurfaceIdentifier(g);
+ if (curSurface != null) {
+ if (j2dSurface != curSurface) {
+ if (joglContext != null) {
+ joglContext.destroy();
+ joglContext = null;
+ joglDrawable = null;
+ sendReshape = true;
+ if (DEBUG) {
+ System.err.println("Sending reshape because surface changed");
+ System.err.println("New surface = " + curSurface);
+ }
+ }
+ j2dSurface = curSurface;
+ }
+ if (joglContext == null) {
+ if (factory.canCreateExternalGLDrawable()) {
+ joglDrawable = factory.createExternalGLDrawable();
+ joglContext = joglDrawable.createContext(shareWith);
+ } else if (factory.canCreateContextOnJava2DSurface()) {
+ // Mac OS X code path
+ joglContext = factory.createContextOnJava2DSurface(g, shareWith);
+ }
+ if (DEBUG) {
+ joglContext.setGL(new DebugGL2(joglContext.getGL().getGL2()));
+ }
+
+ if (Java2D.isFBOEnabled() &&
+ Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT &&
+ fbObjectWorkarounds) {
+ createNewDepthBuffer = true;
+ }
+ }
+ if (joglContext instanceof Java2DGLContext) {
+ // Mac OS X code path
+ ((Java2DGLContext) joglContext).setGraphics(g);
+ }
+
+ if (DEBUG && VERBOSE && Java2D.isFBOEnabled()) {
+ System.err.print("-- Surface type: ");
+ int surfaceType = Java2D.getOGLSurfaceType(g);
+ if (surfaceType == Java2D.UNDEFINED) {
+ System.err.println("UNDEFINED");
+ } else if (surfaceType == Java2D.WINDOW) {
+ System.err.println("WINDOW");
+ } else if (surfaceType == Java2D.PBUFFER) {
+ System.err.println("PBUFFER");
+ } else if (surfaceType == Java2D.TEXTURE) {
+ System.err.println("TEXTURE");
+ } else if (surfaceType == Java2D.FLIP_BACKBUFFER) {
+ System.err.println("FLIP_BACKBUFFER");
+ } else if (surfaceType == Java2D.FBOBJECT) {
+ System.err.println("FBOBJECT");
+ } else {
+ System.err.println("(Unknown surface type " + surfaceType + ")");
+ }
+ }
+
+ drawableHelper.invokeGL(joglDrawable, joglContext, displayAction, initAction);
+ }
+ } finally {
+ j2dContext.release();
+ }
+ }
+ });
+ } else {
+ if (!hardwareAccelerationDisabled) {
+ pbuffer.display();
+ } else {
+ drawableHelper.invokeGL(offscreenDrawable, offscreenContext, displayAction, initAction);
+ }
+
+ if (offscreenImage != null) {
+ // Draw resulting image in one shot
+ g.drawImage(offscreenImage, 0, 0, offscreenImage.getWidth(), offscreenImage.getHeight(), this);
+ }
+ }
+ }
+
+ /** Overridden to track when this component is added to a container.
+ Subclasses which override this method must call
+ super.addNotify() in their addNotify() method in order to
+ function properly. paintComponent
in class javax.swing.JComponent
*/
+ public void addNotify() {
+ super.addNotify();
+ shouldInitialize = true;
+ if (DEBUG) {
+ System.err.println("GL2JPanel.addNotify()");
+ }
+ }
+
+ /** Overridden to track when this component is removed from a
+ container. Subclasses which override this method must call
+ super.removeNotify() in their removeNotify() method in order to
+ function properly. addNotify
in class java.awt.Component
*/
+ public void removeNotify() {
+ if (DEBUG) {
+ System.err.println("GL2JPanel.removeNotify()");
+ }
+ if (oglPipelineEnabled) {
+ Java2D.invokeWithOGLContextCurrent(null, new Runnable() {
+ public void run() {
+ if (joglContext != null) {
+ joglContext.destroy();
+ joglContext = null;
+ }
+ joglDrawable = null;
+ if (j2dContext != null) {
+ j2dContext.destroy();
+ j2dContext = null;
+ }
+ }
+ });
+ } else {
+ if (!hardwareAccelerationDisabled) {
+ if (pbuffer != null) {
+ pbuffer.destroy();
+ pbuffer = null;
+ }
+ } else {
+ if (offscreenContext != null) {
+ offscreenContext.destroy();
+ offscreenContext = null;
+ }
+ if (offscreenDrawable != null) {
+ offscreenDrawable.destroy();
+ offscreenDrawable = null;
+ }
+ }
+ }
+ isInitialized = false;
+ super.removeNotify();
+ }
+
+ /** Overridden to cause {@link GLDrawableHelper#reshape} to be
+ called on all registered {@link GLEventListener}s. Subclasses
+ which override this method must call super.reshape() in
+ their reshape() method in order to function properly. removeNotify
in class java.awt.Component
*/
+ public void reshape(int x, int y, int width, int height) {
+ super.reshape(x, y, width, height);
+
+ reshapeX = x;
+ reshapeY = y;
+ reshapeWidth = width;
+ reshapeHeight = height;
+ handleReshape = true;
+ }
+
+ public void setOpaque(boolean opaque) {
+ if (opaque != isOpaque()) {
+ if (offscreenImage != null) {
+ offscreenImage.flush();
+ offscreenImage = null;
+ }
+ }
+ super.setOpaque(opaque);
+ }
+
+ public void addGLEventListener(GLEventListener listener) {
+ drawableHelper.addGLEventListener(listener);
+ }
+
+ public void removeGLEventListener(GLEventListener listener) {
+ drawableHelper.removeGLEventListener(listener);
+ }
+
+ public GLContext createContext(GLContext shareWith) {
+ if (!hardwareAccelerationDisabled) {
+ return pbuffer.createContext(shareWith);
+ } else {
+ return offscreenDrawable.createContext(shareWith);
+ }
+ }
+
+ public void setRealized(boolean realized) {
+ }
+
+ public GLContext getContext() {
+ if (oglPipelineEnabled) {
+ return joglContext;
+ } else {
+ if (!hardwareAccelerationDisabled) {
+ // Workaround for crashes in NetBeans GUI builder
+ if (pbuffer == null && Beans.isDesignTime()) {
+ return null;
+ }
+ return pbuffer.getContext();
+ } else {
+ return offscreenContext;
+ }
+ }
+ }
+
+ public GL getGL() {
+ GLContext context = getContext();
+ return (context == null) ? null : context.getGL();
+ }
+
+ public void setGL(GL gl) {
+ if(!gl.isGL2()) {
+ throw new GLException("not a GL2 implementation");
+ }
+ GLContext context = getContext();
+ if (context != null) {
+ context.setGL(gl);
+ }
+ }
+
+ public void setAutoSwapBufferMode(boolean onOrOff) {
+ if (!hardwareAccelerationDisabled) {
+ // Workaround for crashes in NetBeans GUI builder
+ if (pbuffer == null && Beans.isDesignTime()) {
+ return;
+ }
+ pbuffer.setAutoSwapBufferMode(onOrOff);
+ } else {
+ drawableHelper.setAutoSwapBufferMode(onOrOff);
+ }
+ }
+
+ public boolean getAutoSwapBufferMode() {
+ if (!hardwareAccelerationDisabled && !oglPipelineEnabled) {
+ return pbuffer.getAutoSwapBufferMode();
+ } else {
+ return drawableHelper.getAutoSwapBufferMode();
+ }
+ }
+
+ public void swapBuffers() {
+ // In the current implementation this is basically a no-op. Both
+ // the pbuffer and pixmap based rendering paths use a single-
+ // buffered surface so swapping the buffers doesn't do anything.
+ // We also don't currently have the provision to skip copying the
+ // data to the Swing portion of the GL2JPanel in any of the
+ // rendering paths.
+ if (oglPipelineEnabled) {
+ // Do nothing
+ } else if (!hardwareAccelerationDisabled) {
+ pbuffer.swapBuffers();
+ } else {
+ drawableHelper.invokeGL(offscreenDrawable, offscreenContext, swapBuffersAction, initAction);
+ }
+ }
+
+ /** For a translucent GL2JPanel (one for which {@link #setOpaque
+ setOpaque}(false) has been called), indicates whether the
+ application should preserve the OpenGL color buffer
+ (GL_COLOR_BUFFER_BIT) for correct rendering of the GL2JPanel and
+ underlying widgets which may show through portions of the
+ GL2JPanel with alpha values less than 1. Most Swing
+ implementations currently expect the GL2JPanel to be completely
+ cleared (e.g., by reshape
in class java.awt.Component
glClear(GL_COLOR_BUFFER_BIT |
+ GL_DEPTH_BUFFER_BIT)
), but for certain optimized Swing
+ implementations which use OpenGL internally, it may be possible
+ to perform OpenGL rendering using the GL2JPanel into the same
+ OpenGL drawable as the Swing implementation uses. */
+ public boolean shouldPreserveColorBufferIfTranslucent() {
+ return oglPipelineEnabled;
+ }
+
+ protected GLCapabilities caps=null;
+
+ public GLCapabilities getChosenGLCapabilities() {
+ if (oglPipelineEnabled) {
+ if(caps==null) {
+ caps = new GLCapabilities();
+ }
+ return caps;
+ }
+
+ if (hardwareAccelerationDisabled) {
+ if (offscreenDrawable != null)
+ return offscreenDrawable.getChosenGLCapabilities();
+ } else {
+ if (pbuffer != null)
+ return pbuffer.getChosenGLCapabilities();
+ }
+
+ return null;
+ }
+
+ public void setChosenGLCapabilities(GLCapabilities caps) {
+ if (oglPipelineEnabled) {
+ this.caps = (caps==null) ? null : (GLCapabilities) caps.clone();
+ }
+
+ if (hardwareAccelerationDisabled) {
+ if (offscreenDrawable != null)
+ offscreenDrawable.setChosenGLCapabilities(caps);
+ } else {
+ if (pbuffer != null)
+ pbuffer.setChosenGLCapabilities(caps);
+ }
+ }
+
+ public NativeWindow getNativeWindow() {
+ throw new GLException("FIXME");
+ }
+
+ public GLDrawableFactory getFactory() {
+ return factory;
+ }
+
+ public int lockSurface() throws GLException {
+ throw new GLException("FIXME");
+ }
+ public void unlockSurface() {
+ throw new GLException("FIXME");
+ }
+ public boolean isSurfaceLocked() {
+ throw new GLException("FIXME");
+ }
+ public void destroy() {
+ throw new GLException("FIXME");
+ }
+
+ //----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ private void initialize() {
+ if (panelWidth == 0 ||
+ panelHeight == 0) {
+ // See whether we have a non-zero size yet and can go ahead with
+ // initialization
+ if (reshapeWidth == 0 ||
+ reshapeHeight == 0) {
+ return;
+ }
+
+ // Pull down reshapeWidth and reshapeHeight into panelWidth and
+ // panelHeight eagerly in order to complete initialization, and
+ // force a reshape later
+ panelWidth = reshapeWidth;
+ panelHeight = reshapeHeight;
+ }
+
+ if (!oglPipelineEnabled) {
+ // Initialize either the hardware-accelerated rendering path or
+ // the lightweight rendering path
+ if (!hardwareAccelerationDisabled) {
+ if (factory.canCreateGLPbuffer()) {
+ if (pbuffer != null) {
+ throw new InternalError("Creating pbuffer twice without destroying it (memory leak / correctness bug)");
+ }
+ try {
+ pbuffer = factory.createGLPbuffer(offscreenCaps,
+ null,
+ pbufferWidth,
+ pbufferHeight,
+ shareWith);
+ updater = new Updater();
+ pbuffer.addGLEventListener(updater);
+ shouldInitialize = false;
+ isInitialized = true;
+ return;
+ } catch (GLException e) {
+ if (DEBUG) {
+ e.printStackTrace();
+ System.err.println("GL2JPanel: Falling back on software rendering because of problems creating pbuffer");
+ }
+ hardwareAccelerationDisabled = true;
+ }
+ } else {
+ if (DEBUG) {
+ System.err.println("GL2JPanel: Falling back on software rendering because no pbuffer support");
+ }
+
+ // If the factory reports that it can't create a pbuffer,
+ // don't try again the next time, and fall through to the
+ // software rendering path
+ hardwareAccelerationDisabled = true;
+ }
+ }
+
+ if (softwareRenderingDisabled) {
+ throw new GLException("Fallback to software rendering disabled by user");
+ }
+
+ // Fall-through path: create an offscreen context instead
+ offscreenDrawable = factory.createOffscreenDrawable(offscreenCaps, chooser);
+ offscreenDrawable.setSize(Math.max(1, panelWidth), Math.max(1, panelHeight));
+ offscreenContext = (GLContextImpl) offscreenDrawable.createContext(shareWith);
+ offscreenContext.setSynchronized(true);
+ }
+ updater = new Updater();
+ shouldInitialize = false;
+ isInitialized = true;
+ }
+
+ private void handleReshape() {
+ readBackWidthInPixels = 0;
+ readBackHeightInPixels = 0;
+
+ panelWidth = reshapeWidth;
+ panelHeight = reshapeHeight;
+
+ if (DEBUG) {
+ System.err.println("GL2JPanel.handleReshape: (w,h) = (" +
+ panelWidth + "," + panelHeight + ")");
+ }
+
+ sendReshape = true;
+
+ if (!oglPipelineEnabled) {
+ if (!hardwareAccelerationDisabled) {
+ // Use factor larger than 2 during shrinks for some hysteresis
+ float shrinkFactor = 2.5f;
+ if ((panelWidth > pbufferWidth ) || (panelHeight > pbufferHeight) ||
+ (panelWidth < (pbufferWidth / shrinkFactor)) || (panelHeight < (pbufferHeight / shrinkFactor))) {
+ if (DEBUG) {
+ System.err.println("Resizing pbuffer from (" + pbufferWidth + ", " + pbufferHeight + ") " +
+ " to fit (" + panelWidth + ", " + panelHeight + ")");
+ }
+ // Must destroy and recreate pbuffer to fit
+ if (pbuffer != null) {
+ // Watch for errors during pbuffer destruction (due to
+ // buggy / bad OpenGL drivers, in particular SiS) and fall
+ // back to software rendering
+ try {
+ pbuffer.destroy();
+ } catch (GLException e) {
+ hardwareAccelerationDisabled = true;
+ if (DEBUG) {
+ System.err.println("WARNING: falling back to software rendering due to bugs in OpenGL drivers");
+ e.printStackTrace();
+ }
+ }
+ }
+ pbuffer = null;
+ isInitialized = false;
+ pbufferWidth = getNextPowerOf2(panelWidth);
+ pbufferHeight = getNextPowerOf2(panelHeight);
+ if (DEBUG && !hardwareAccelerationDisabled) {
+ System.err.println("New pbuffer size is (" + pbufferWidth + ", " + pbufferHeight + ")");
+ }
+ initialize();
+ }
+
+ // It looks like NVidia's drivers (at least the ones on my
+ // notebook) are buggy and don't allow a rectangle of less than
+ // the pbuffer's width to be read...this doesn't really matter
+ // because it's the Graphics.drawImage() calls that are the
+ // bottleneck. Should probably make the size of the offscreen
+ // image be the exact size of the pbuffer to save some work on
+ // resize operations...
+ if (!hardwareAccelerationDisabled) {
+ readBackWidthInPixels = pbufferWidth;
+ readBackHeightInPixels = panelHeight;
+ } else {
+ // Just disabled hardware acceleration during this resize operation; do a fixup
+ readBackWidthInPixels = Math.max(1, panelWidth);
+ readBackHeightInPixels = Math.max(1, panelHeight);
+ }
+ } else {
+ offscreenContext.destroy();
+ offscreenDrawable.setSize(Math.max(1, panelWidth), Math.max(1, panelHeight));
+ readBackWidthInPixels = Math.max(1, panelWidth);
+ readBackHeightInPixels = Math.max(1, panelHeight);
+ }
+
+ if (offscreenImage != null) {
+ offscreenImage.flush();
+ offscreenImage = null;
+ }
+ }
+
+ handleReshape = false;
+ }
+
+ // FIXME: it isn't clear whether this works any more given that
+ // we're accessing the GLDrawable inside of the GLPbuffer directly
+ // up in reshape() -- need to rethink and clean this up
+ class Updater implements GLEventListener {
+ private Graphics g;
+
+ public void setGraphics(Graphics g) {
+ this.g = g;
+ }
+
+ public void init(GLAutoDrawable drawable) {
+ if (oglPipelineEnabled) {
+ if (!preGL(g)) {
+ return;
+ }
+ }
+ drawableHelper.init(GL2JPanel.this);
+ if (oglPipelineEnabled) {
+ postGL(g);
+ }
+ }
+
+ public void display(GLAutoDrawable drawable) {
+ if (oglPipelineEnabled) {
+ if (!preGL(g)) {
+ return;
+ }
+ }
+
+ if (sendReshape) {
+ if (DEBUG) {
+ System.err.println("glViewport(" + viewportX + ", " + viewportY + ", " + panelWidth + ", " + panelHeight + ")");
+ }
+ getGL().getGL2().glViewport(viewportX, viewportY, panelWidth, panelHeight);
+ drawableHelper.reshape(GL2JPanel.this, viewportX, viewportY, panelWidth, panelHeight);
+ sendReshape = false;
+ }
+
+ drawableHelper.display(GL2JPanel.this);
+
+ if (!oglPipelineEnabled) {
+ // Must now copy pixels from offscreen context into surface
+ if (offscreenImage == null) {
+ if (panelWidth > 0 && panelHeight > 0) {
+ // It looks like NVidia's drivers (at least the ones on my
+ // notebook) are buggy and don't allow a sub-rectangle to be
+ // read from a pbuffer...this doesn't really matter because
+ // it's the Graphics.drawImage() calls that are the
+ // bottleneck
+
+ int awtFormat = 0;
+ int hwGLFormat = 0;
+ if (!hardwareAccelerationDisabled) {
+ // This seems to be a good choice on all platforms
+ hwGLFormat = GL2.GL_UNSIGNED_INT_8_8_8_8_REV;
+ }
+
+ // Should be more flexible in these BufferedImage formats;
+ // perhaps see what the preferred image types are on the
+ // given platform
+ if (isOpaque()) {
+ awtFormat = BufferedImage.TYPE_INT_RGB;
+ } else {
+ awtFormat = BufferedImage.TYPE_INT_ARGB;
+ }
+
+ offscreenImage = new BufferedImage(panelWidth,
+ panelHeight,
+ awtFormat);
+ switch (awtFormat) {
+ case BufferedImage.TYPE_3BYTE_BGR:
+ glFormat = GL2.GL_BGR;
+ glType = GL2.GL_UNSIGNED_BYTE;
+ readBackBytes = ByteBuffer.allocate(readBackWidthInPixels * readBackHeightInPixels * 3);
+ break;
+
+ case BufferedImage.TYPE_INT_RGB:
+ case BufferedImage.TYPE_INT_ARGB:
+ glFormat = GL2.GL_BGRA;
+ glType = (hardwareAccelerationDisabled
+ ? offscreenContext.getOffscreenContextPixelDataType()
+ : hwGLFormat);
+ readBackInts = IntBuffer.allocate(readBackWidthInPixels * readBackHeightInPixels);
+ break;
+
+ default:
+ // FIXME: Support more off-screen image types (current
+ // offscreen context implementations don't use others, and
+ // some of the OpenGL formats aren't supported in the 1.1
+ // headers, which we're currently using)
+ throw new GLException("Unsupported offscreen image type " + awtFormat);
+ }
+ }
+ }
+
+ if (offscreenImage != null) {
+ GL2 gl = getGL().getGL2();
+ // Save current modes
+ gl.glGetIntegerv(GL2.GL_PACK_SWAP_BYTES, swapbytes, 0);
+ gl.glGetIntegerv(GL2.GL_PACK_ROW_LENGTH, rowlength, 0);
+ gl.glGetIntegerv(GL2.GL_PACK_SKIP_ROWS, skiprows, 0);
+ gl.glGetIntegerv(GL2.GL_PACK_SKIP_PIXELS, skippixels, 0);
+ gl.glGetIntegerv(GL2.GL_PACK_ALIGNMENT, alignment, 0);
+
+ gl.glPixelStorei(GL2.GL_PACK_SWAP_BYTES, GL2.GL_FALSE);
+ gl.glPixelStorei(GL2.GL_PACK_ROW_LENGTH, readBackWidthInPixels);
+ gl.glPixelStorei(GL2.GL_PACK_SKIP_ROWS, 0);
+ gl.glPixelStorei(GL2.GL_PACK_SKIP_PIXELS, 0);
+ gl.glPixelStorei(GL2.GL_PACK_ALIGNMENT, 1);
+
+ // Actually read the pixels.
+ gl.glReadBuffer(GL2.GL_FRONT);
+ if (readBackBytes != null) {
+ gl.glReadPixels(0, 0, readBackWidthInPixels, readBackHeightInPixels, glFormat, glType, readBackBytes);
+ } else if (readBackInts != null) {
+ gl.glReadPixels(0, 0, readBackWidthInPixels, readBackHeightInPixels, glFormat, glType, readBackInts);
+ }
+
+ // Restore saved modes.
+ gl.glPixelStorei(GL2.GL_PACK_SWAP_BYTES, swapbytes[0]);
+ gl.glPixelStorei(GL2.GL_PACK_ROW_LENGTH, rowlength[0]);
+ gl.glPixelStorei(GL2.GL_PACK_SKIP_ROWS, skiprows[0]);
+ gl.glPixelStorei(GL2.GL_PACK_SKIP_PIXELS, skippixels[0]);
+ gl.glPixelStorei(GL2.GL_PACK_ALIGNMENT, alignment[0]);
+
+ if (readBackBytes != null || readBackInts != null) {
+ // Copy temporary data into raster of BufferedImage for faster
+ // blitting Note that we could avoid this copy in the cases
+ // where !offscreenContext.offscreenImageNeedsVerticalFlip(),
+ // but that's the software rendering path which is very slow
+ // anyway
+ Object src = null;
+ Object dest = null;
+ int srcIncr = 0;
+ int destIncr = 0;
+
+ if (readBackBytes != null) {
+ src = readBackBytes.array();
+ dest = ((DataBufferByte) offscreenImage.getRaster().getDataBuffer()).getData();
+ srcIncr = readBackWidthInPixels * 3;
+ destIncr = offscreenImage.getWidth() * 3;
+ } else {
+ src = readBackInts.array();
+ dest = ((DataBufferInt) offscreenImage.getRaster().getDataBuffer()).getData();
+ srcIncr = readBackWidthInPixels;
+ destIncr = offscreenImage.getWidth();
+ }
+
+ if (!hardwareAccelerationDisabled ||
+ offscreenContext.offscreenImageNeedsVerticalFlip()) {
+ int srcPos = 0;
+ int destPos = (offscreenImage.getHeight() - 1) * destIncr;
+ for (; destPos >= 0; srcPos += srcIncr, destPos -= destIncr) {
+ System.arraycopy(src, srcPos, dest, destPos, destIncr);
+ }
+ } else {
+ int srcPos = 0;
+ int destEnd = destIncr * offscreenImage.getHeight();
+ for (int destPos = 0; destPos < destEnd; srcPos += srcIncr, destPos += destIncr) {
+ System.arraycopy(src, srcPos, dest, destPos, destIncr);
+ }
+ }
+
+ // Note: image will be drawn back in paintComponent() for
+ // correctness on all platforms
+ }
+ }
+ } else {
+ // Cause OpenGL pipeline to flush its results because
+ // otherwise it's possible we will buffer up multiple frames'
+ // rendering results, resulting in apparent mouse lag
+ GL2 gl = getGL().getGL2();
+ gl.glFinish();
+
+ postGL(g);
+ }
+ }
+
+ public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
+ // This is handled above and dispatched directly to the appropriate context
+ }
+
+ public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
+ }
+ }
+
+ class InitAction implements Runnable {
+ public void run() {
+ updater.init(GL2JPanel.this);
+ }
+ }
+ private InitAction initAction = new InitAction();
+
+ class DisplayAction implements Runnable {
+ public void run() {
+ updater.display(GL2JPanel.this);
+ }
+ }
+ private DisplayAction displayAction = new DisplayAction();
+
+ // This one is used exclusively in the non-hardware-accelerated case
+ class SwapBuffersAction implements Runnable {
+ public void run() {
+ offscreenDrawable.swapBuffers();
+ }
+ }
+ private SwapBuffersAction swapBuffersAction = new SwapBuffersAction();
+
+ class PaintImmediatelyAction implements Runnable {
+ public void run() {
+ paintImmediately(0, 0, getWidth(), getHeight());
+ }
+ }
+ private PaintImmediatelyAction paintImmediatelyAction = new PaintImmediatelyAction();
+
+ private int getNextPowerOf2(int number) {
+ // Workaround for problems where 0 width or height are transiently
+ // seen during layout
+ if (number == 0) {
+ return 2;
+ }
+
+ if (((number-1) & number) == 0) {
+ //ex: 8 -> 0b1000; 8-1=7 -> 0b0111; 0b1000&0b0111 == 0
+ return number;
+ }
+ int power = 0;
+ while (number > 0) {
+ number = number>>1;
+ power++;
+ }
+ return (1<
The Animator class creates a background thread in which the
+ calls to display()
are performed. After each drawable
+ has been redrawn, a brief pause is performed to avoid swamping the
+ CPU, unless {@link #setRunAsFastAsPossible} has been called.
limit() - position()
) in the passed ByteBuffer into
+ a newly-allocated direct ByteBuffer. The returned buffer will
+ have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static ByteBuffer copyByteBuffer(ByteBuffer orig) {
+ ByteBuffer dest = newByteBuffer(orig.remaining());
+ dest.put(orig);
+ dest.rewind();
+ return dest;
+ }
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed FloatBuffer
+ into a newly-allocated direct FloatBuffer. The returned buffer
+ will have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static FloatBuffer copyFloatBuffer(FloatBuffer orig) {
+ return copyFloatBufferAsByteBuffer(orig).asFloatBuffer();
+ }
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed IntBuffer
+ into a newly-allocated direct IntBuffer. The returned buffer
+ will have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static IntBuffer copyIntBuffer(IntBuffer orig) {
+ return copyIntBufferAsByteBuffer(orig).asIntBuffer();
+ }
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed ShortBuffer
+ into a newly-allocated direct ShortBuffer. The returned buffer
+ will have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static ShortBuffer copyShortBuffer(ShortBuffer orig) {
+ return copyShortBufferAsByteBuffer(orig).asShortBuffer();
+ }
+
+ //----------------------------------------------------------------------
+ // Copy routines (type-to-ByteBuffer)
+ //
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed FloatBuffer
+ into a newly-allocated direct ByteBuffer. The returned buffer
+ will have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static ByteBuffer copyFloatBufferAsByteBuffer(FloatBuffer orig) {
+ ByteBuffer dest = newByteBuffer(orig.remaining() * SIZEOF_FLOAT);
+ dest.asFloatBuffer().put(orig);
+ dest.rewind();
+ return dest;
+ }
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed IntBuffer into
+ a newly-allocated direct ByteBuffer. The returned buffer will
+ have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static ByteBuffer copyIntBufferAsByteBuffer(IntBuffer orig) {
+ ByteBuffer dest = newByteBuffer(orig.remaining() * SIZEOF_INT);
+ dest.asIntBuffer().put(orig);
+ dest.rewind();
+ return dest;
+ }
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed ShortBuffer
+ into a newly-allocated direct ByteBuffer. The returned buffer
+ will have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static ByteBuffer copyShortBufferAsByteBuffer(ShortBuffer orig) {
+ ByteBuffer dest = newByteBuffer(orig.remaining() * SIZEOF_SHORT);
+ dest.asShortBuffer().put(orig);
+ dest.rewind();
+ return dest;
+ }
+
+ //----------------------------------------------------------------------
+ // Conversion routines
+ //
+
+ public final static float[] getFloatArray(double[] source) {
+ int i=source.length;
+ float[] dest = new float[i--];
+ while(i>=0) { dest[i]=(float)source[i]; i--; }
+ return dest;
+ }
+
+ public final static FloatBuffer getFloatBuffer(DoubleBuffer source) {
+ source.rewind();
+ FloatBuffer dest = BufferUtil.newFloatBuffer(source.limit());
+ while(source.hasRemaining()) { dest.put((float)source.get()); }
+ return dest;
+ }
+
+
+ //----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ // NOTE that this work must be done reflectively at the present time
+ // because this code must compile and run correctly on both CDC/FP and J2SE
+ private static boolean isCDCFP;
+ private static Class byteOrderClass;
+ private static Object nativeOrderObject;
+ private static Method orderMethod;
+
+ private static void nativeOrder(ByteBuffer buf) {
+ if (!isCDCFP) {
+ try {
+ if (byteOrderClass == null) {
+ byteOrderClass = Class.forName("java.nio.ByteOrder");
+ orderMethod = ByteBuffer.class.getMethod("order", new Class[] { byteOrderClass });
+ Method nativeOrderMethod = byteOrderClass.getMethod("nativeOrder", null);
+ nativeOrderObject = nativeOrderMethod.invoke(null, null);
+ }
+ } catch (Throwable t) {
+ // Must be running on CDC / FP
+ isCDCFP = true;
+ }
+
+ if (!isCDCFP) {
+ try {
+ orderMethod.invoke(buf, new Object[] { nativeOrderObject });
+ } catch (Throwable t) {
+ }
+ }
+ }
+ }
+}
diff --git a/src/classes/javax/media/opengl/util/BufferUtil.java.javase b/src/classes/javax/media/opengl/util/BufferUtil.java.javase
new file mode 100755
index 000000000..2c49d5fae
--- /dev/null
+++ b/src/classes/javax/media/opengl/util/BufferUtil.java.javase
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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 Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ *
+ * Sun gratefully acknowledges that this software was originally authored
+ * and developed by Kenneth Bradley Russell and Christopher John Kline.
+ */
+
+package javax.media.opengl.util;
+
+import java.nio.*;
+import java.util.*;
+
+import java.lang.reflect.*;
+
+/** Utility routines for dealing with direct buffers. */
+
+public class BufferUtil {
+ public static final int SIZEOF_BYTE = 1;
+ public static final int SIZEOF_SHORT = 2;
+ public static final int SIZEOF_INT = 4;
+ public static final int SIZEOF_FLOAT = 4;
+ public static final int SIZEOF_LONG = 8;
+ public static final int SIZEOF_DOUBLE = 8;
+
+ private BufferUtil() {}
+
+ //----------------------------------------------------------------------
+ // Allocation routines
+ //
+
+ /** Allocates a new direct ByteBuffer with the specified number of
+ elements. The returned buffer will have its byte order set to
+ the host platform's native byte order. */
+ public static ByteBuffer newByteBuffer(int numElements) {
+ ByteBuffer bb = ByteBuffer.allocateDirect(numElements);
+ nativeOrder(bb);
+ return bb;
+ }
+
+ public static ByteBuffer newByteBuffer(byte[] values) {
+ ByteBuffer bb = newByteBuffer(values.length);
+ bb.put(values);
+ bb.rewind();
+ return bb;
+ }
+
+ /** Allocates a new direct DoubleBuffer with the specified number of
+ elements. The returned buffer will have its byte order set to
+ the host platform's native byte order. */
+ public static DoubleBuffer newDoubleBuffer(int numElements) {
+ ByteBuffer bb = newByteBuffer(numElements * SIZEOF_DOUBLE);
+ return bb.asDoubleBuffer();
+ }
+
+ /** Allocates a new direct FloatBuffer with the specified number of
+ elements. The returned buffer will have its byte order set to
+ the host platform's native byte order. */
+ public static FloatBuffer newFloatBuffer(int numElements) {
+ ByteBuffer bb = newByteBuffer(numElements * SIZEOF_FLOAT);
+ return bb.asFloatBuffer();
+ }
+
+ public static FloatBuffer newFloatBuffer(float[] values) {
+ FloatBuffer bb = newFloatBuffer(values.length);
+ bb.put(values);
+ bb.rewind();
+ return bb;
+ }
+
+ /** Allocates a new direct IntBuffer with the specified number of
+ elements. The returned buffer will have its byte order set to
+ the host platform's native byte order. */
+ public static IntBuffer newIntBuffer(int numElements) {
+ ByteBuffer bb = newByteBuffer(numElements * SIZEOF_INT);
+ return bb.asIntBuffer();
+ }
+
+ public static IntBuffer newIntBuffer(int[] values) {
+ IntBuffer bb = newIntBuffer(values.length);
+ bb.put(values);
+ bb.rewind();
+ return bb;
+ }
+
+ /** Allocates a new direct LongBuffer with the specified number of
+ elements. The returned buffer will have its byte order set to
+ the host platform's native byte order. */
+ public static LongBuffer newLongBuffer(int numElements) {
+ ByteBuffer bb = newByteBuffer(numElements * SIZEOF_LONG);
+ return bb.asLongBuffer();
+ }
+
+ /** Allocates a new direct ShortBuffer with the specified number of
+ elements. The returned buffer will have its byte order set to
+ the host platform's native byte order. */
+ public static ShortBuffer newShortBuffer(int numElements) {
+ ByteBuffer bb = newByteBuffer(numElements * SIZEOF_SHORT);
+ return bb.asShortBuffer();
+ }
+
+ public static ShortBuffer newShortBuffer(short[] values) {
+ ShortBuffer bb = newShortBuffer(values.length);
+ bb.put(values);
+ bb.rewind();
+ return bb;
+ }
+
+ //----------------------------------------------------------------------
+ // Copy routines (type-to-type)
+ //
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed ByteBuffer into
+ a newly-allocated direct ByteBuffer. The returned buffer will
+ have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static ByteBuffer copyByteBuffer(ByteBuffer orig) {
+ ByteBuffer dest = newByteBuffer(orig.remaining());
+ dest.put(orig);
+ dest.rewind();
+ return dest;
+ }
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed FloatBuffer
+ into a newly-allocated direct FloatBuffer. The returned buffer
+ will have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static FloatBuffer copyFloatBuffer(FloatBuffer orig) {
+ return copyFloatBufferAsByteBuffer(orig).asFloatBuffer();
+ }
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed IntBuffer
+ into a newly-allocated direct IntBuffer. The returned buffer
+ will have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static IntBuffer copyIntBuffer(IntBuffer orig) {
+ return copyIntBufferAsByteBuffer(orig).asIntBuffer();
+ }
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed ShortBuffer
+ into a newly-allocated direct ShortBuffer. The returned buffer
+ will have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static ShortBuffer copyShortBuffer(ShortBuffer orig) {
+ return copyShortBufferAsByteBuffer(orig).asShortBuffer();
+ }
+
+ //----------------------------------------------------------------------
+ // Copy routines (type-to-ByteBuffer)
+ //
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed FloatBuffer
+ into a newly-allocated direct ByteBuffer. The returned buffer
+ will have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static ByteBuffer copyFloatBufferAsByteBuffer(FloatBuffer orig) {
+ ByteBuffer dest = newByteBuffer(orig.remaining() * SIZEOF_FLOAT);
+ dest.asFloatBuffer().put(orig);
+ dest.rewind();
+ return dest;
+ }
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed IntBuffer into
+ a newly-allocated direct ByteBuffer. The returned buffer will
+ have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static ByteBuffer copyIntBufferAsByteBuffer(IntBuffer orig) {
+ ByteBuffer dest = newByteBuffer(orig.remaining() * SIZEOF_INT);
+ dest.asIntBuffer().put(orig);
+ dest.rewind();
+ return dest;
+ }
+
+ /** Copies the remaining elements (as defined by
+ limit() - position()
) in the passed ShortBuffer
+ into a newly-allocated direct ByteBuffer. The returned buffer
+ will have its byte order set to the host platform's native byte
+ order. The position of the newly-allocated buffer will be zero,
+ and the position of the passed buffer is unchanged (though its
+ mark is changed). */
+ public static ByteBuffer copyShortBufferAsByteBuffer(ShortBuffer orig) {
+ ByteBuffer dest = newByteBuffer(orig.remaining() * SIZEOF_SHORT);
+ dest.asShortBuffer().put(orig);
+ dest.rewind();
+ return dest;
+ }
+
+ //----------------------------------------------------------------------
+ // Conversion routines
+ //
+
+ public final static float[] getFloatArray(double[] source) {
+ int i=source.length;
+ float[] dest = new float[i--];
+ while(i>=0) { dest[i]=(float)source[i]; i--; }
+ return dest;
+ }
+
+ public final static FloatBuffer getFloatBuffer(DoubleBuffer source) {
+ source.rewind();
+ FloatBuffer dest = BufferUtil.newFloatBuffer(source.limit());
+ while(source.hasRemaining()) { dest.put((float)source.get()); }
+ return dest;
+ }
+
+ //----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ // NOTE that this work must be done reflectively at the present time
+ // because this code must compile and run correctly on both CDC/FP and J2SE
+ private static boolean isCDCFP;
+ private static Class byteOrderClass;
+ private static Object nativeOrderObject;
+ private static Method orderMethod;
+
+ private static void nativeOrder(ByteBuffer buf) {
+ if (!isCDCFP) {
+ try {
+ if (byteOrderClass == null) {
+ byteOrderClass = Class.forName("java.nio.ByteOrder");
+ orderMethod = ByteBuffer.class.getMethod("order", new Class[] { byteOrderClass });
+ Method nativeOrderMethod = byteOrderClass.getMethod("nativeOrder", null);
+ nativeOrderObject = nativeOrderMethod.invoke(null, null);
+ }
+ } catch (Throwable t) {
+ // Must be running on CDC / FP
+ isCDCFP = true;
+ }
+
+ if (!isCDCFP) {
+ try {
+ orderMethod.invoke(buf, new Object[] { nativeOrderObject });
+ } catch (Throwable t) {
+ }
+ }
+ }
+ }
+}
diff --git a/src/classes/javax/media/opengl/util/FPSAnimator.java b/src/classes/javax/media/opengl/util/FPSAnimator.java
new file mode 100755
index 000000000..a00a96424
--- /dev/null
+++ b/src/classes/javax/media/opengl/util/FPSAnimator.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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 Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ *
+ * Sun gratefully acknowledges that this software was originally authored
+ * and developed by Kenneth Bradley Russell and Christopher John Kline.
+ */
+
+package javax.media.opengl.util;
+
+import java.util.*;
+import javax.media.opengl.*;
+
+/** An Animator subclass which attempts to achieve a target
+ frames-per-second rate to avoid using all CPU time. The target FPS
+ is only an estimate and is not guaranteed. */
+
+public class FPSAnimator extends Animator {
+ private Timer timer;
+ private int fps;
+ private boolean scheduleAtFixedRate;
+
+ /** Creates an FPSAnimator with a given target frames-per-second
+ value. Equivalent to FPSAnimator(null, fps)
. */
+ public FPSAnimator(int fps) {
+ this(null, fps);
+ }
+
+ /** Creates an FPSAnimator with a given target frames-per-second
+ value and a flag indicating whether to use fixed-rate
+ scheduling. Equivalent to FPSAnimator(null, fps,
+ scheduleAtFixedRate)
. */
+ public FPSAnimator(int fps, boolean scheduleAtFixedRate) {
+ this(null, fps, scheduleAtFixedRate);
+ }
+
+ /** Creates an FPSAnimator with a given target frames-per-second
+ value and an initial drawable to animate. Equivalent to
+ FPSAnimator(null, fps, false)
. */
+ public FPSAnimator(GLAutoDrawable drawable, int fps) {
+ this(drawable, fps, false);
+ }
+
+ /** Creates an FPSAnimator with a given target frames-per-second
+ value, an initial drawable to animate, and a flag indicating
+ whether to use fixed-rate scheduling. */
+ public FPSAnimator(GLAutoDrawable drawable, int fps, boolean scheduleAtFixedRate) {
+ this.fps = fps;
+ if (drawable != null) {
+ add(drawable);
+ }
+ this.scheduleAtFixedRate = scheduleAtFixedRate;
+ }
+
+ /** Starts this FPSAnimator. */
+ public synchronized void start() {
+ if (timer != null) {
+ throw new GLException("Already started");
+ }
+ timer = new Timer();
+ long delay = (long) (1000.0f / (float) fps);
+ TimerTask task = new TimerTask() {
+ public void run() {
+ display();
+ }
+ };
+ if (scheduleAtFixedRate) {
+ timer.scheduleAtFixedRate(task, 0, delay);
+ } else {
+ timer.schedule(task, 0, delay);
+ }
+ }
+
+ /** Indicates whether this FPSAnimator is currently running. This
+ should only be used as a heuristic to applications because in
+ some circumstances the FPSAnimator may be in the process of
+ shutting down and this method will still return true. */
+ public synchronized boolean isAnimating() {
+ return (timer != null);
+ }
+
+ /** Stops this FPSAnimator. Due to the implementation of the
+ FPSAnimator it is not guaranteed that the FPSAnimator will be
+ completely stopped by the time this method returns. */
+ public synchronized void stop() {
+ if (timer == null) {
+ throw new GLException("Already stopped");
+ }
+ timer.cancel();
+ timer = null;
+ }
+}
diff --git a/src/classes/javax/media/opengl/util/Gamma.java b/src/classes/javax/media/opengl/util/Gamma.java
new file mode 100755
index 000000000..201144e3f
--- /dev/null
+++ b/src/classes/javax/media/opengl/util/Gamma.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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 Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ *
+ * Sun gratefully acknowledges that this software was originally authored
+ * and developed by Kenneth Bradley Russell and Christopher John Kline.
+ */
+
+package javax.media.opengl.util;
+
+import com.sun.opengl.impl.*;
+
+/** Provides control over the primary display's gamma, brightness and
+ contrast controls via the hardware gamma ramp tables. Not
+ supported on all platforms or graphics hardware. + + Thanks to the LWJGL project for illustrating how to access gamma + control on the various platforms. +*/ + +public class Gamma { + private Gamma() {} + + /** + * Sets the gamma, brightness, and contrast of the current main + * display. This functionality is not available on all platforms and + * graphics hardware. Returns true if the settings were successfully + * changed, false if not. This method may return false for some + * values of the incoming arguments even on hardware which does + * support the underlying functionality.
+ *
+ * If this method returns true, the display settings will
+ * automatically be reset to their original values upon JVM exit
+ * (assuming the JVM does not crash); if the user wishes to change
+ * the display settings back to normal ahead of time, use {@link
+ * #resetDisplayGamma resetDisplayGamma}(). It is recommended to
+ * call {@link #resetDisplayGamma resetDisplayGamma} before calling
+ * e.g. System.exit()
from the application rather than
+ * rely on the shutdown hook functionality due to inevitable race
+ * conditions and unspecified behavior during JVM teardown.
+ * + * This method may be called multiple times during the application's + * execution, but calling {@link #resetDisplayGamma + * resetDisplayGamma} will only reset the settings to the values + * before the first call to this method.
+ *
+ * @param gamma The gamma value, typically > 1.0 (default values
+ * vary, but typically roughly 1.0)
+ * @param brightness The brightness value between -1.0 and 1.0,
+ * inclusive (default values vary, but typically 0)
+ * @param contrast The contrast, greater than 0.0 (default values
+ * vary, but typically 1)
+ * @return true if gamma settings were successfully changed, false
+ * if not
+ * @throws IllegalArgumentException if any of the parameters were
+ * out-of-bounds
+ */
+ public static boolean setDisplayGamma(float gamma, float brightness, float contrast) throws IllegalArgumentException {
+ return GLDrawableFactoryImpl.getFactoryImpl(false).setDisplayGamma(gamma, brightness, contrast);
+ }
+
+ /**
+ * Resets the gamma, brightness and contrast values for the primary
+ * display to their original values before {@link #setDisplayGamma
+ * setDisplayGamma} was called the first time. {@link
+ * #setDisplayGamma setDisplayGamma} must be called before calling
+ * this method or an unspecified exception will be thrown. While it
+ * is not explicitly required that this method be called before
+ * exiting, calling it is recommended because of the inevitable
+ * unspecified behavior during JVM teardown.
+ */
+ public static void resetDisplayGamma() {
+ GLDrawableFactoryImpl.getFactoryImpl(false).resetDisplayGamma();
+ }
+}
diff --git a/src/classes/javax/media/opengl/util/ImmModeSink.java b/src/classes/javax/media/opengl/util/ImmModeSink.java
new file mode 100644
index 000000000..7201cec65
--- /dev/null
+++ b/src/classes/javax/media/opengl/util/ImmModeSink.java
@@ -0,0 +1,318 @@
+
+package javax.media.opengl.util;
+
+import javax.media.opengl.*;
+import java.nio.*;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+public class ImmModeSink {
+
+ public static final boolean DEBUG_BEGIN_END = false;
+ public static final boolean DEBUG_DRAW = false;
+ public static final boolean FLOAT2FIXED = false;
+
+ public static final int GL_QUADS = 0x0007;
+ public static final int GL_QUAD_STRIP = 0x0008;
+ public static final int GL_POLYGON = 0x0009;
+
+ public ImmModeSink(int glDataType, int glDrawUsage,
+ int vComps, int nComps, int cComps, int tComps, int initialSize) {
+
+ vboSet = new VBOSet(glDataType, glDrawUsage, vComps, nComps, cComps, tComps, initialSize);
+ this.vboSetList = new ArrayList();
+ }
+
+ private void destroyList(GL gl) {
+ for(Iterator i=vboSetList.iterator(); i.hasNext() ; ) {
+ ((VBOSet)i.next()).destroy(gl);
+ }
+ vboSetList.clear();
+ }
+
+ public void destroy(GL gl) {
+ destroyList(gl);
+
+ vboSet.destroy(gl);
+ }
+
+ public void reset() {
+ reset(null);
+ }
+
+ public void reset(GL gl) {
+ destroyList(gl);
+ vboSet.reset(gl);
+ }
+
+ public String toString() {
+ return "ImmModeSink[listsz: "+vboSetList.size()+
+ ",\n"+vboSet+
+ "]";
+ }
+
+ public void draw(GL gl, boolean disableBufferAfterDraw) {
+ if(DEBUG_DRAW) {
+ Exception e = new Exception("ImmModeSink.draw(disableBufferAfterDraw: "+disableBufferAfterDraw+"):\n\t"+this);
+ e.printStackTrace();
+ }
+ int n=0;
+ for(Iterator i=vboSetList.iterator(); i.hasNext() ; n++) {
+ ((VBOSet)i.next()).draw(gl, disableBufferAfterDraw, n);
+ }
+ }
+
+ public void glBegin(int mode) {
+ if(DEBUG_BEGIN_END) {
+ Exception e = new Exception("ImmModeSink.glBegin("+vboSet.mode+"):\n\t"+this);
+ e.printStackTrace();
+ }
+ vboSet.modeOrig = mode;
+ switch(mode) {
+ case GL_QUADS:
+ mode=GL.GL_TRIANGLE_STRIP;
+ break;
+ case GL_QUAD_STRIP:
+ mode=GL.GL_TRIANGLE_STRIP;
+ break;
+ case GL_POLYGON:
+ mode=GL.GL_LINES;
+ break;
+ }
+ vboSet.mode = mode;
+ vboSet.checkSeal(false);
+ }
+
+ public final void glEnd(GL gl) {
+ glEnd(gl, true);
+ }
+
+ public void glEnd(GL gl, boolean immediateDraw) {
+ if(DEBUG_BEGIN_END) {
+ Exception e = new Exception("ImmModeSink START glEnd(immediate: "+immediateDraw+"):\n\t"+this);
+ e.printStackTrace();
+ }
+ if(immediateDraw) {
+ vboSet.seal(gl, false);
+ vboSet.draw(gl, true, -1);
+ reset(gl);
+ } else {
+ vboSet.seal(gl, true);
+ vboSetList.add(vboSet);
+ vboSet = vboSet.regenerate();
+ }
+ }
+
+ public final void glVertex2f(float x, float y) {
+ vboSet.glVertex2f(x,y);
+ }
+
+ public final void glVertex3f(float x, float y, float z) {
+ vboSet.glVertex3f(x,y,z);
+ }
+
+ public final void glNormal3f(float x, float y, float z) {
+ vboSet.glNormal3f(x,y,z);
+ }
+
+ public final void glColor3f(float x, float y, float z) {
+ vboSet.glColor3f(x,y,z);
+ }
+
+ public final void glTexCoord2f(float x, float y) {
+ vboSet.glTexCoord2f(x,y);
+ }
+
+ public final void glTexCoord3f(float x, float y, float z) {
+ vboSet.glTexCoord3f(x,y,z);
+ }
+
+ private VBOSet vboSet;
+ private ArrayList vboSetList;
+
+ protected static class VBOSet {
+ protected VBOSet(int glDataType, int glDrawUsage,
+ int vComps, int nComps, int cComps, int tComps, int initialSize) {
+ nComps = 0;
+ tComps = 0;
+ if(FLOAT2FIXED && glDataType==GL.GL_FLOAT) {
+ glDataType=GL.GL_FIXED;
+ }
+ this.glDataType=glDataType;
+ this.glDrawUsage=glDrawUsage;
+ this.vComps=vComps;
+ this.nComps=nComps;
+ this.cComps=cComps;
+ this.tComps=tComps;
+ this.initialSize=initialSize;
+
+ this.vertexVBO = VBOBufferDraw.create(GL2ES1.GL_VERTEX_ARRAY, glDataType, glDrawUsage, vComps, initialSize);
+ this.normalVBO = VBOBufferDraw.create(GL2ES1.GL_NORMAL_ARRAY, glDataType, glDrawUsage, nComps, initialSize);
+ this.colorVBO = VBOBufferDraw.create(GL2ES1.GL_COLOR_ARRAY, glDataType, glDrawUsage, cComps, initialSize);
+ this.texcoordVBO = VBOBufferDraw.create(GL2ES1.GL_TEXTURE_COORD_ARRAY, glDataType, glDrawUsage, tComps, initialSize);
+
+ this.sealed=false;
+ this.mode = -1;
+ this.modeOrig = -1;
+ }
+
+ protected final VBOSet regenerate() {
+ return new VBOSet(glDataType, glDrawUsage, vComps, nComps, cComps, tComps, initialSize);
+ }
+
+ protected void destroy(GL gl) {
+ vertexVBO.destroy(gl);
+ normalVBO.destroy(gl);
+ colorVBO.destroy(gl);
+ texcoordVBO.destroy(gl);
+
+ this.mode = -1;
+ this.modeOrig = -1;
+ this.sealed=false;
+ }
+
+ protected void reset(GL gl) {
+ vertexVBO.reset(gl);
+ normalVBO.reset(gl);
+ colorVBO.reset(gl);
+ texcoordVBO.reset(gl);
+
+ this.mode = -1;
+ this.modeOrig = -1;
+ this.sealed=false;
+ }
+
+ public String toString() {
+ return "VBOSet[mode "+mode+
+ ", modeOrig "+modeOrig+
+ ", sealed "+sealed+
+ ",\n\t vertexVBO "+vertexVBO+
+ ",\n\t normalVBO "+normalVBO+
+ ",\n\t colorVBO "+colorVBO+
+ ",\n\t texcoordVBO "+texcoordVBO+
+ "]";
+ }
+
+ protected void checkSeal(boolean test) throws GLException {
+ if(mode<0) {
+ throw new GLException("No mode set yet, call glBegin(mode) first:\n\t"+this);
+ }
+ if(sealed!=test) {
+ if(test) {
+ throw new GLException("Not Sealed yet, call glEnd() first:\n\t"+this);
+ } else {
+ throw new GLException("Already Sealed, can't modify VBO after glEnd():\n\t"+this);
+ }
+ }
+ }
+
+ protected void rewind() {
+ checkSeal(true);
+
+ vertexVBO.rewind();
+ normalVBO.rewind();
+ colorVBO.rewind();
+ texcoordVBO.rewind();
+ }
+
+ protected void seal(GL gl, boolean disableBufferAfterSeal)
+ {
+ checkSeal(false);
+ sealed = true;
+
+ vertexVBO.seal(gl, disableBufferAfterSeal);
+ normalVBO.seal(gl, disableBufferAfterSeal);
+ colorVBO.seal(gl, disableBufferAfterSeal);
+ texcoordVBO.seal(gl, disableBufferAfterSeal);
+ }
+
+ protected void draw(GL gl, boolean disableBufferAfterDraw, int i)
+ {
+ if(DEBUG_DRAW) {
+ Exception e = new Exception("ImmModeSink.draw["+i+"](disableBufferAfterDraw: "+disableBufferAfterDraw+"):\n\t"+this);
+ e.printStackTrace();
+ }
+ vertexVBO.enableBuffer(gl);
+ normalVBO.enableBuffer(gl);
+ colorVBO.enableBuffer(gl);
+ texcoordVBO.enableBuffer(gl);
+
+ if (vertexVBO.getBuffer()!=null) {
+ gl.glDrawArrays(mode, 0, vertexVBO.getVerticeNumber());
+ }
+
+ if(disableBufferAfterDraw) {
+ vertexVBO.disableBuffer(gl);
+ normalVBO.disableBuffer(gl);
+ colorVBO.disableBuffer(gl);
+ texcoordVBO.disableBuffer(gl);
+ }
+ }
+
+ protected void glVertex2f(float x, float y) {
+ checkSeal(false);
+ vertexVBO.putf(x);
+ if(vertexVBO.getComponents()>1)
+ vertexVBO.putf(y);
+ vertexVBO.padding(2);
+ }
+
+ protected void glVertex3f(float x, float y, float z) {
+ checkSeal(false);
+ vertexVBO.putf(x);
+ if(vertexVBO.getComponents()>1)
+ vertexVBO.putf(y);
+ if(vertexVBO.getComponents()>2)
+ vertexVBO.putf(z);
+ vertexVBO.padding(3);
+ }
+
+ protected void glNormal3f(float x, float y, float z) {
+ checkSeal(false);
+ normalVBO.putf(x);
+ if(normalVBO.getComponents()>1)
+ normalVBO.putf(y);
+ if(normalVBO.getComponents()>2)
+ normalVBO.putf(z);
+ normalVBO.padding(3);
+ }
+
+ protected void glColor3f(float x, float y, float z) {
+ checkSeal(false);
+ colorVBO.putf(x);
+ if(colorVBO.getComponents()>1)
+ colorVBO.putf(y);
+ if(colorVBO.getComponents()>2)
+ colorVBO.putf(z);
+ colorVBO.padding(3);
+ }
+
+ protected void glTexCoord2f(float x, float y) {
+ checkSeal(false);
+ texcoordVBO.putf(x);
+ if(texcoordVBO.getComponents()>1)
+ texcoordVBO.putf(y);
+ texcoordVBO.padding(2);
+ }
+
+ protected void glTexCoord3f(float x, float y, float z) {
+ checkSeal(false);
+ texcoordVBO.putf(x);
+ if(texcoordVBO.getComponents()>1)
+ texcoordVBO.putf(y);
+ if(texcoordVBO.getComponents()>2)
+ texcoordVBO.putf(z);
+ texcoordVBO.padding(3);
+ }
+
+ VBOBufferDraw vertexVBO;
+ VBOBufferDraw normalVBO;
+ VBOBufferDraw colorVBO;
+ VBOBufferDraw texcoordVBO;
+ int mode, modeOrig;
+ int glDataType, glDrawUsage, vComps, nComps, cComps, tComps, initialSize;
+ boolean sealed;
+ }
+
+}
+
diff --git a/src/classes/javax/media/opengl/util/VBOBufferDraw.java b/src/classes/javax/media/opengl/util/VBOBufferDraw.java
new file mode 100644
index 000000000..7a7a3d9a6
--- /dev/null
+++ b/src/classes/javax/media/opengl/util/VBOBufferDraw.java
@@ -0,0 +1,379 @@
+
+package javax.media.opengl.util;
+
+import javax.media.opengl.*;
+import javax.media.opengl.util.gl2es1.VBOBufferDrawGL2ES1;
+import java.nio.*;
+
+public abstract class VBOBufferDraw {
+
+ public static VBOBufferDraw create(int glArrayType, int glDataType, int glBufferUsage, int comps, int initialSize)
+ throws GLException
+ {
+ if(GLProfile.isGL2ES1()) {
+ return new VBOBufferDrawGL2ES1(glArrayType, glDataType, glBufferUsage, comps, initialSize);
+ }
+ throw new GLException("VBOBufferDraw not supported for profile: "+GLProfile.getProfile());
+ }
+
+ protected void init(int glArrayType, int glDataType, int glBufferUsage, int comps, int initialSize)
+ throws GLException
+ {
+ switch(glArrayType) {
+ case GL2ES1.GL_VERTEX_ARRAY:
+ case GL2ES1.GL_NORMAL_ARRAY:
+ case GL2ES1.GL_COLOR_ARRAY:
+ case GL2ES1.GL_TEXTURE_COORD_ARRAY:
+ break;
+ default:
+ throw new GLException("invalid glArrayType: "+glArrayType+":\n\t"+this);
+ }
+ this.glArrayType = glArrayType;
+ this.glDataType = glDataType;
+ this.clazz = getBufferClass(glDataType);
+ this.buffer = null;
+ this.components = comps;
+ this.initialSize = initialSize;
+ if( ! (GLProfile.isGL2ES2() && glBufferUsage==GL2ES2.GL_STREAM_DRAW) ) {
+ switch(glBufferUsage) {
+ case GL2ES1.GL_STATIC_DRAW:
+ case GL2ES1.GL_DYNAMIC_DRAW:
+ break;
+ default:
+ throw new GLException("invalid glBufferUsage: "+glBufferUsage+":\n\t"+this);
+ }
+ }
+ this.glBufferUsage = glBufferUsage;
+ this.vboName = 0;
+ this.sealed=false;
+ this.bufferEnabled=false;
+ growVBO(initialSize);
+ }
+
+ public int getGLArrayType() {
+ return glArrayType;
+ }
+
+ public int getGlDataType() {
+ return glDataType;
+ }
+
+ public int getComponents() {
+ return components;
+ }
+
+ public Class getBufferClass() {
+ return clazz;
+ }
+
+ public Buffer getBuffer() {
+ return buffer;
+ }
+
+ public int getBufferUsage() {
+ return glBufferUsage;
+ }
+
+ public void destroy(GL gl) {
+ reset(gl);
+ if(vboName!=0) {
+ int[] tmp = new int[1];
+ tmp[0] = vboName;
+ gl.glDeleteBuffers(1, tmp, 0);
+ vboName = 0;
+ }
+ }
+
+ public void reset() {
+ reset(null);
+ }
+
+ public void reset(GL gl) {
+ if(gl!=null) {
+ disableBuffer(gl);
+ }
+ this.sealed=false;
+ if(buffer!=null) {
+ buffer.clear();
+ }
+ }
+
+ private final void init_vbo(GL gl) {
+ if(vboName==0) {
+ int[] tmp = new int[1];
+ gl.glGenBuffers(1, tmp, 0);
+ vboName = tmp[0];
+ }
+ }
+
+ private final void checkSeal(boolean test) throws GLException {
+ if(sealed!=test) {
+ if(test) {
+ throw new GLException("Not Sealed yet, seal first:\n\t"+this);
+ } else {
+ throw new GLException("Already Sealed, can't modify VBO:\n\t"+this);
+ }
+ }
+ }
+
+ public final boolean growVBOIfNecessary(int spare) {
+ if(buffer==null) {
+ throw new GLException("buffer no configured:\n\t"+this);
+ }
+ if(buffer!=null && buffer.remaining() An Animator can be attached to one or more {@link
+ GLAutoDrawable}s to drive their display() methods in a loop. The Animator class creates a background thread in which the
+ calls to
+ *
+ * The steps for deploying such applets are straightforward. First,
+ * the "archive" parameter to the applet tag must contain jogl.jar
+ * and gluegen-rt.jar, as well as any jar files associated with your
+ * applet (in this case, "your_applet.jar").
+ *
+ * Second, the codebase directory on the server, which contains the
+ * applet's jar files, must also contain jogl.jar, gluegen-rt.jar,
+ * and all of the jogl-natives-*.jar and gluegen-rt-natives-*.jar
+ * files from the standard JOGL and GlueGen runtime distributions
+ * (provided in jogl-[version]-webstart.zip from the JOGL
+ * release builds and gluegen-rt-[version]-webstart.zip from the
+ * GlueGen
+ * runtime release builds). Note that the codebase of the applet
+ * is currently the location from which the JOGL native library used
+ * by the applet is downloaded. All of the JOGL and GlueGen-related
+ * jars must be signed by the same entity, which is typically Sun
+ * Microsystems, Inc.
+ *
+ * To deploy an applet using both JOGL and JOAL, simply add joal.jar
+ * to the list of jars in the archive tag of the applet, and put
+ * joal.jar and the joal-natives-*.jar signed jars into the same
+ * codebase directory on the web server. These signed jars are
+ * supplied in the joal-[version]-webstart.zip archive from the JOAL
+ * release builds.
+ *
+ * Sample applet code:
+ *
+ *
+ * There are some limitations with this approach. It is not possible
+ * to specify e.g. -Dsun.java2d.noddraw=true or
+ * -Dsun.java2d.opengl=true for better control over the Java2D
+ * pipeline as it is with Java Web Start. However, the
+ * JOGLAppletLauncher tries to force the use of
+ * -Dsun.java2d.noddraw=true on Windows platforms for best robustness
+ * by detecting if it has not been set and asking the user whether it
+ * can update the Java Plug-In configuration automatically. If the
+ * user agrees to this, a browser restart is required in order for the
+ * change to take effect, though it is permanent for subsequent
+ * browser restarts.
+ *
+ * The behavior of the noddraw-related dialog box can be changed via
+ * two applet parameters. The
+ *
+ * The JOGL (and optionally JOAL) natives are cached in the user's
+ * home directory (the value of the "user.home" system property in
+ * Java) under the directory .jogl_ext. The Java Plug-In is
+ * responsible for performing all other jar caching. If the JOGL
+ * installation is updated on the server, the .jogl_ext cache will
+ * automatically be updated.
+ *
+ * This technique requires that JOGL has not been installed in to the
+ * JRE under e.g. jre/lib/ext. If problems are seen when deploying
+ * this applet launcher, the first question to ask the end user is
+ * whether jogl.jar and any associated DLLs, .so's, etc. are installed
+ * directly in to the JRE. The applet launcher has been tested
+ * primarily under Mozilla, Firefox and Internet Explorer; there may
+ * be problems when running under, for example, Opera.
+ *
+ * It has been discovered that the Talkback agent in Mozilla / Firefox
+ * has bad interactions with OpenGL applets. For highest performance,
+ * we recommend disabling the Talkback agent; find talkback.exe, run
+ * it, and follow the directions for turning it off. Please see
+ * this
+ * thread on the javagaming.org forums and
+ * this
+ * thread on the Mozilla bug reporting database.
+ *
+ * @author Lilian Chamontin
+ * @author Kenneth Russell
+ */
+public class JOGLAppletLauncher extends Applet {
+ static {
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception ignore) {
+ }
+ }
+
+ // metadata for native libraries
+ private static class NativeLibInfo {
+ private String osName;
+ private String osArch;
+ private String osNameAndArchPair;
+ private String nativePrefix;
+ private String nativeSuffix;
+
+ public NativeLibInfo(String osName, String osArch, String osNameAndArchPair, String nativePrefix, String nativeSuffix) {
+ this.osName = osName;
+ this.osArch = osArch;
+ this.osNameAndArchPair = osNameAndArchPair;
+ this.nativePrefix = nativePrefix;
+ this.nativeSuffix = nativeSuffix;
+ }
+
+ public boolean matchesOSAndArch(String osName, String osArch) {
+ if (osName.toLowerCase().startsWith(this.osName)) {
+ if ((this.osArch == null) ||
+ (osArch.toLowerCase().equals(this.osArch))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean matchesNativeLib(String fileName) {
+ if (fileName.toLowerCase().endsWith(nativeSuffix)) {
+ return true;
+ }
+ return false;
+ }
+
+ public String formatNativeJarName(String nativeJarPattern) {
+ return MessageFormat.format(nativeJarPattern, new Object[] { osNameAndArchPair });
+ }
+
+ public String getNativeLibName(String baseName) {
+ return nativePrefix + baseName + nativeSuffix;
+ }
+
+ public boolean isMacOS() {
+ return (osName.equals("mac"));
+ }
+
+ public boolean mayNeedDRIHack() {
+ return (!isMacOS() && !osName.equals("win"));
+ }
+ }
+
+ private static final NativeLibInfo[] allNativeLibInfo = {
+ new NativeLibInfo("win", "x86", "windows-i586", "", ".dll"),
+ new NativeLibInfo("win", "amd64", "windows-amd64", "", ".dll"),
+ new NativeLibInfo("win", "x86_64","windows-amd64", "", ".dll"),
+ new NativeLibInfo("mac", "ppc", "macosx-ppc", "lib", ".jnilib"),
+ new NativeLibInfo("mac", "i386", "macosx-universal", "lib", ".jnilib"),
+ new NativeLibInfo("linux", "i386", "linux-i586", "lib", ".so"),
+ new NativeLibInfo("linux", "x86", "linux-i586", "lib", ".so"),
+ new NativeLibInfo("linux", "amd64", "linux-amd64", "lib", ".so"),
+ new NativeLibInfo("linux", "x86_64","linux-amd64", "lib", ".so"),
+ new NativeLibInfo("sunos", "sparc", "solaris-sparc", "lib", ".so"),
+ new NativeLibInfo("sunos", "sparcv9","solaris-sparcv9", "lib", ".so"),
+ new NativeLibInfo("sunos", "x86", "solaris-i586", "lib", ".so"),
+ new NativeLibInfo("sunos", "amd64", "solaris-amd64", "lib", ".so"),
+ new NativeLibInfo("sunos", "x86_64","solaris-amd64", "lib", ".so")
+ };
+
+ private NativeLibInfo nativeLibInfo;
+ // Library names computed once the jar comes down.
+ // The signatures of these native libraries are checked before
+ // installing them.
+ private String[] nativeLibNames;
+
+ /** The applet we have to start */
+ private Applet subApplet;
+
+ private String subAppletClassName; // from applet PARAM
+ private String subAppletDisplayName; // from applet PARAM
+ /** URL string to an image used while installing */
+ private String subAppletImageName; // from applet PARAM
+
+ private String installDirectory; // (defines a private directory for native libs)
+
+ private JPanel loaderPanel = new JPanel(new BorderLayout());
+
+ private JProgressBar progressBar = new JProgressBar(0,100);
+
+ private boolean isInitOk = false;
+
+ /** false once start() has been invoked */
+ private boolean firstStart = true;
+
+ /** true if start() has passed successfully */
+ private boolean joglStarted = false;
+
+ /** Indicates whether JOAL is present */
+ private boolean haveJOAL = false;
+
+ // Helpers for question about whether to update deployment.properties
+ private static final String JRE_PREFIX = "deployment.javapi.jre.";
+ private static final String NODDRAW_PROP = "-Dsun.java2d.noddraw=true";
+ private static final String DONT_ASK = ".dont_ask";
+
+ public JOGLAppletLauncher() {
+ }
+
+ private static String md2Hash(String str) {
+ // Helps hash the jars in the "archive" tag into a hex value to
+ // avoid having too-long path names in the install directory's
+ // path name but also to have unique directories for each
+ // different archive set used (also meaning for each class loader
+ // loading something via the JOGLAppletLauncher) -- note that this
+ // is somewhat dependent on the Sun implementation of applets and
+ // their class loaders
+ MessageDigest md2 = null;
+ try {
+ md2 = MessageDigest.getInstance("MD2");
+ } catch (NoSuchAlgorithmException e) {
+ return "";
+ }
+ byte[] digest = md2.digest(str.getBytes());
+ if (digest == null || (digest.length == 0))
+ return "";
+ StringBuffer res = new StringBuffer();
+ for (int i = 0; i < digest.length; i++) {
+ res.append(Integer.toHexString(digest[i] & 0xFF));
+ }
+ return res.toString();
+ }
+
+ /** Applet initialization */
+ public void init() {
+
+ this.subAppletClassName = getParameter("subapplet.classname");
+ if (subAppletClassName == null){
+ displayError("Init failed : Missing subapplet.classname argument");
+ return;
+ }
+ this.subAppletDisplayName = getParameter("subapplet.displayname");
+ if (subAppletDisplayName == null){
+ subAppletDisplayName = "Applet";
+ }
+
+ this.subAppletImageName = getParameter("subapplet.image");
+
+ initLoaderLayout();
+ validate();
+
+ String extForm = getCodeBase().toExternalForm();
+ String codeBase = extForm.substring(extForm.indexOf(":") + 3); // minus http:// or https://
+
+ this.installDirectory = codeBase.replace(':', '_')
+ .replace('.', '_').replace('/', '_').replace('~','_') // clean up the name
+ + md2Hash(getParameter("archive")); // make it unique across different applet class loaders
+
+ String osName = System.getProperty("os.name");
+ String osArch = System.getProperty("os.arch");
+ if (checkOSAndArch(osName, osArch)) {
+ this.isInitOk = true;
+ } else {
+ displayError("Init failed : Unsupported os / arch ( " + osName + " / " + osArch + " )");
+ }
+ }
+
+ private void displayMessage(final String message){
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ progressBar.setString(message);
+ }
+ });
+ }
+
+ private void displayError(final String errorMessage){
+ // Print message to Java console too in case it's truncated in the applet's display
+ System.err.println(errorMessage);
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ progressBar.setString("Error : " + errorMessage);
+ }
+ });
+ }
+
+ private void setProgress(final int value) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ progressBar.setValue(value);
+ }
+ });
+ }
+
+ private void initLoaderLayout(){
+ setLayout(new BorderLayout());
+ progressBar.setBorderPainted(true);
+ progressBar.setStringPainted(true);
+ progressBar.setString("Loading...");
+ boolean includeImage = false;
+ ImageIcon image = null;
+ if (subAppletImageName != null){
+ try {
+ image = new ImageIcon(new URL(subAppletImageName));
+ includeImage = true;
+ } catch (MalformedURLException ex) {
+ ex.printStackTrace();
+ // not blocking
+ }
+ }
+ if (includeImage){
+ add(loaderPanel, BorderLayout.SOUTH);
+ loaderPanel.add(new JLabel(image), BorderLayout.CENTER);
+ loaderPanel.add(progressBar, BorderLayout.SOUTH);
+ } else {
+ add(loaderPanel, BorderLayout.SOUTH);
+ loaderPanel.add(progressBar, BorderLayout.CENTER);
+ }
+ }
+
+
+ /** start asynchroneous loading of libraries if needed */
+ public void start(){
+ if (isInitOk){
+ if (firstStart) {
+ firstStart = false;
+ String userHome = System.getProperty("user.home");
+
+ try {
+ // We need to load in the jogl package so that we can query the version information
+ ClassLoader classloader = getClass().getClassLoader();
+ classloader.loadClass("javax.media.opengl.GL");
+ Package p = Package.getPackage("javax.media.opengl");
+
+ String installDirName = userHome + File.separator + ".jogl_ext"
+ + File.separator + installDirectory + File.separator + p.getImplementationVersion().replace(':', '_');
+
+ final File installDir = new File(installDirName);
+
+ Thread refresher = new Thread() {
+ public void run() {
+ refreshJOGL(installDir);
+ }
+ };
+ refresher.setPriority(Thread.NORM_PRIORITY - 1);
+ refresher.start();
+ }
+ catch (ClassNotFoundException e) {
+ System.err.println("Unable to load javax.media.opengl package");
+ System.exit(0);
+ }
+
+ } else if (joglStarted) {
+ checkNoDDrawAndUpdateDeploymentProperties();
+ // we have to start again the applet (start can be called multiple times,
+ // e.g once per tabbed browsing
+ subApplet.start();
+ }
+ }
+ }
+
+ public void stop(){
+ if (subApplet != null){
+ subApplet.stop();
+ }
+ }
+
+ public void destroy(){
+ if (subApplet != null){
+ subApplet.destroy();
+ }
+ }
+
+
+ /** Helper method to make it easier to call methods on the
+ sub-applet from JavaScript. */
+ public Applet getSubApplet() {
+ return subApplet;
+ }
+
+ private boolean checkOSAndArch(String osName, String osArch) {
+ for (int i = 0; i < allNativeLibInfo.length; i++) {
+ NativeLibInfo info = allNativeLibInfo[i];
+ if (info.matchesOSAndArch(osName, osArch)) {
+ nativeLibInfo = info;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Get a "boolean" parameter, assuming that anything non-null aside
+ // from "false" is true
+ private boolean getBooleanParameter(String parameterName) {
+ String val = getParameter(parameterName);
+ if (val == null)
+ return false;
+ return !val.toLowerCase().equals("false");
+ }
+
+ private void checkNoDDrawAndUpdateDeploymentProperties() {
+ if (getBooleanParameter("jogl.disable.noddraw.check"))
+ return;
+ if (System.getProperty("os.name").toLowerCase().startsWith("windows") &&
+ !"true".equalsIgnoreCase(System.getProperty("sun.java2d.noddraw"))) {
+ if (!SwingUtilities.isEventDispatchThread()) {
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ updateDeploymentPropertiesImpl();
+ }
+ });
+ } catch (Exception e) {
+ }
+ } else {
+ updateDeploymentPropertiesImpl();
+ }
+ }
+ }
+
+ private void updateDeploymentPropertiesImpl() {
+ String userHome = System.getProperty("user.home");
+ File dontAskFile = new File(userHome + File.separator + ".jogl_ext" +
+ File.separator + DONT_ASK);
+ if (dontAskFile.exists())
+ return; // User asked us not to prompt again
+
+ int option = 0;
+
+ if (!getBooleanParameter("jogl.silent.noddraw.check")) {
+ option = JOptionPane.showOptionDialog(null,
+ "For best robustness of JOGL applets on Windows,\n" +
+ "we recommend disabling Java2D's use of DirectDraw.\n" +
+ "This setting will affect all applets, but is unlikely\n" +
+ "to slow other applets down significantly. May we update\n" +
+ "your deployment.properties to turn off DirectDraw for\n" +
+ "applets? You can change this back later if necessary\n" +
+ "using the Java Control Panel, Java tab, under Java\n" +
+ "Applet Runtime Settings.",
+ "Update deployment.properties?",
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ new Object[] {
+ "Yes",
+ "No",
+ "No, Don't Ask Again"
+ },
+ "Yes");
+ }
+
+ if (option < 0 ||
+ option == 1)
+ return; // No
+
+ if (option == 2) {
+ try {
+ dontAskFile.createNewFile();
+ } catch (IOException e) {
+ }
+ return; // No, Don't Ask Again
+ }
+
+ try {
+ // Must update deployment.properties
+ File propsDir = new File(System.getProperty("user.home") + File.separator +
+ "Application Data/Sun/Java/Deployment");
+ if (!propsDir.exists())
+ // Don't know what's going on or how to set this permanently
+ return;
+
+ File propsFile = new File(propsDir, "deployment.properties");
+ if (!propsFile.exists())
+ // Don't know what's going on or how to set this permanently
+ return;
+
+ Properties props = new Properties();
+ InputStream input = new BufferedInputStream(new FileInputStream(propsFile));
+ props.load(input);
+ input.close();
+ // Search through the keys looking for JRE versions
+ Set/*display()
are performed. After each drawable
+ has been redrawn, a brief pause is performed to avoid swamping the
+ CPU, unless {@link #setRunAsFastAsPossible} has been called.
+ * <applet code="com.sun.opengl.util.JOGLAppletLauncher"
+ * width=600
+ * height=400
+ * codebase="/lib"
+ * archive="jogl.jar,gluegen-rt.jar,your_applet.jar">
+ * <param name="subapplet.classname" VALUE="untrusted.JOGLApplet">
+ * <param name="subapplet.displayname" VALUE="My JOGL Applet">
+ * <param name="progressbar" value="true">
+ * <param name="cache_archive" VALUE="jogl.jar,gluegen-rt.jar,your_applet.jar">
+ * <param name="cache_archive_ex" VALUE="jogl.jar;preload,gluegen-rt.jar;preload,your_applet.jar;preload">
+ * </applet>
+ *
+ * jogl.silent.noddraw.check
+ * parameter, if set to "true"
, silences the two dialog
+ * boxes associated with this check, forcing it to always be performed
+ * and deployment.properties to be silently updated if necessary
+ * (unless the user previously saw such a dialog box and dismissed it
+ * by saying "No, Don't Ask Again"). The noddraw check can be disabled
+ * completely by setting the jogl.disable.noddraw.check
+ * applet parameter to "true"
.