I have small app that have a tab widget holding several JPanels, each holding a GLCanvas. After getting a VBO setup and working in one, I found my app would crash (SIGSEGV) if I moved from tab to another. I figured this had something to do with trying to share the VBO and/or shaders across contexts. To solve the problem, I figured I would change the way I create the GLCanvas, by sending it a context from a previous GLCanvas if it already exists.
capabilities = new GLCapabilities(profile)
//initialize a GLDrawable of your choice
if (PanelGL.context == null) {
canvas = new GLCanvas(capabilities)
PanelGL.context = canvas.getContext()
}
else {
new GLCanvas(capabilities,
new DefaultGLCapabilitiesChooser(),
PanelGL.context,
null)
}
This fixed the error, but now on the second and third GLCanvases their screens are completely green. The green makes sense as right now I just have it hard-coded into the fragment shader, but on the first screen while I see the bunny model, on the second and third it’s just green. I don’t know if it’s a bug in my setup or if the camera is just too close to the model (although I’m guessing it’s more my setup).
As I am running Linux, I have tried adding the “-Djogl.GLContext.optimize” as recommended in the JOGL User Guide, but that just made my program exit instead of getting the green screen.
Is this the correct way to share a context across multiple CLCanvas instances? Do I need to do anything different with the VBO and shaders to share them as well?
That should be the proper way to share an OpenGL canvas, at least in the way you’re calling the constructor. The code itself doesn’t make sense because in one condition you’re assigning the first GLCanvas to a canvas variable, and in the other you’re just doing new GLCanvas(). Maybe that was just for the purposes of the post?
Here are some things to try:
Make sure your first GLCanvas is visible before you use it to create the other GLCanvas’s, because it might not have set its GLContext up correctly until it is added to a visible component (that’s a maybe, most times GLCanvas is pretty good about having a context right away).
Check the behavior of the tab widget to see if switching tabs indirectly calls addNotify() and removeNotify() on the GLCanvas’s. You can do this by extending the GLCanvas class, overriding those two methods to print out statements and then call super.XNotify().
Use a GLPBuffer as the shared context. You create a 1x1 context in the beginning of your code and all GLCanvases share with it.
I tried that with limited success. I overrode the setVisible function and added the GLCanvas then. I store it’s context and try using it when the other two pop up. I get an image on the first panel but nothing at all on the second two panels. Would I have more success using GLJPanel?
What exactly am I checking for?
Doing this, I got a ton of stuff dumped to the screen, but didn’t get an image in any of the GLCanvas’s that tried to use the context.
Look for removeNotify() being called on the GLCanvas’s when they are hidden by another tab, and addNotify() being called when a GLCanvas should be made visible. This might suggest that the contexts are being destroyed and remade each time a tab is switched. Alternatively, it could appear to be multiple addNotify() in the beginning without any removeNotify() which would suggest that the GLCanvas’s are initialized once internally and aren’t destroyed by tabbing.
This looks like you have some debugging enabled in JOGL and these look like the capability options for the pbuffer.
If you could send me some code, here or by PM, I could take a more thorough look through it.
A pbuffer’s context is theoretically more stable for use with context sharing (it won’t go away due to the window system or anything like that).
Looking at your code, nothing jumped out at me, but I also don’t experience with scala so I can’t be certain. Do you continue to get the odd rendering behavior with using the pbuffer? It could be that there is a bug in your rendering code. When contexts are shared, the objects like vbos and textures and shaders are shared, but the opengl state is not, so you might have to make sure you’re properly setting up the camera and matrices for each context when you render.
Maybe if I made a simpler example. If the JPanel below were to be a GLEventListener with a GLCanas inside it, how would one set it up so all instances shared the same context as a pbuffer?
import javax.swing.*;
import java.awt.*;
public class Test extends JPanel
{
public static void main(String[] args) {
Test test = new Test();
JFrame frame = new JFrame("test");
JTabbedPane pane = new JTabbedPane();
pane.addTab("Tab 1", new Test());
pane.addTab("Tab 2", new Test());
pane.addTab("Tab 3", new Test());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(pane);
frame.setSize(800,600);
frame.setVisible(true);
}
JTabbedPane tabbedPane;
Test() {
}
}
So I took your shell and filled in the details and on my computer everything gets shared correctly when USE_SHARING is true. Just paste it into a file named Test.java and it should compile and run if jogl is setup.
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.media.opengl.DefaultGLCapabilitiesChooser;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLDrawableFactory;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLPbuffer;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import javax.swing.JFrame;
import javax.swing.JTabbedPane;
import com.jogamp.opengl.util.FPSAnimator;
public class Test {
public static boolean USE_SHARING = true;
public static void main(String[] args) {
// make pbuffer
GLProfile profile = GLProfile.get(GLProfile.GL2);
GLCapabilities glCaps = new GLCapabilities(profile);
glCaps.setPBuffer(true);
GLPbuffer pbuffer = GLDrawableFactory.getFactory(profile).createGLPbuffer(glCaps,
new DefaultGLCapabilitiesChooser(),
1, 1, null);
// make 3 canvases
glCaps = new GLCapabilities(profile);
GLCanvas canvas1, canvas2, canvas3;
if (USE_SHARING) {
canvas1 = new GLCanvas(glCaps, new DefaultGLCapabilitiesChooser(), pbuffer.getContext(), null);
canvas2 = new GLCanvas(glCaps, new DefaultGLCapabilitiesChooser(), pbuffer.getContext(), null);
canvas3 = new GLCanvas(glCaps, new DefaultGLCapabilitiesChooser(), pbuffer.getContext(), null);
} else {
canvas1 = new GLCanvas(glCaps);
canvas2 = new GLCanvas(glCaps);
canvas3 = new GLCanvas(glCaps);
}
canvas1.addGLEventListener(new TestRenderer(new float[] { 1f, 0f, 0f }));
canvas2.addGLEventListener(new TestRenderer(new float[] { 0f, 1f, 0f }));
canvas3.addGLEventListener(new TestRenderer(new float[] { 0f, 0f, 1f }));
// set up frame and tabs
JFrame frame = new JFrame();
JTabbedPane pane = new JTabbedPane();
pane.addTab("Red Canvas", canvas1);
pane.addTab("Green Canvas", canvas2);
pane.addTab("Blue Canvas", canvas3);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(pane);
frame.setSize(800, 600);
frame.setVisible(true);
new FPSAnimator(canvas1, 10).start();
new FPSAnimator(canvas2, 10).start();
new FPSAnimator(canvas3, 10).start();
}
private static class TestRenderer implements GLEventListener {
private static int textureId = -1;
private float[] color;
public TestRenderer(float[] color) {
this.color = color;
}
@Override
public void display(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
// set custom background color to distinguish tabs
gl.glClearColor(color[0], color[1], color[2], 1f);
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glEnable(GL.GL_TEXTURE_2D);
gl.glBindTexture(GL.GL_TEXTURE_2D, textureId);
// make this white so the texture appears correctly
// if the texture wasn't shared properly, the quad will be all white
gl.glColor4f(1f, 1f, 1f, 1f);
gl.glBegin(GL2.GL_QUADS);
gl.glVertex3f(-1f, 1f, 0f); gl.glTexCoord2f(0f, 1f);
gl.glVertex3f(-1f, -1f, 0f); gl.glTexCoord2f(0f, 0f);
gl.glVertex3f(1f, -1f, 0f); gl.glTexCoord2f(1f, 0f);
gl.glVertex3f(1f, 1f, 0f); gl.glTexCoord2f(1f, 1f);
gl.glEnd();
gl.glDisable(GL.GL_TEXTURE_2D);
gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
}
@Override
public void dispose(GLAutoDrawable drawable) {
// do nothing
}
@Override
public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
System.out.println("Initializing GLCanvas@" + Integer.toHexString(drawable.hashCode()));
if (textureId < 0) {
int[] id = new int[1];
gl.glGenTextures(1, id, 0);
textureId = id[0];
System.out.println("Creating texture on GLCanvas@" + Integer.toHexString(drawable.hashCode()) + " with ID=" + textureId);
// fill up pixel image with a gradient pattern
gl.glBindTexture(GL.GL_TEXTURE_2D, textureId);
int width = 16;
int height = 16;
FloatBuffer data = ByteBuffer.allocateDirect(4 * 3 * width * height).order(ByteOrder.nativeOrder()).asFloatBuffer();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
data.put(3 * (y * width + x) + 0, x / (float) width);
data.put(3 * (y * width + x) + 1, y / (float) height);
data.put(3 * (y * width + x) + 2, 0f);
}
}
gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGB, width, height, 0, GL.GL_RGB, GL.GL_FLOAT, data.rewind());
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
}
}
@Override
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
GL2 gl = drawable.getGL().getGL2();
gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(-2, 2, -2, 2, -1, 1);
gl.glMatrixMode(GL2.GL_MODELVIEW);
gl.glLoadIdentity();
}
}
}
The main two differences I can see are calling setPBuffer(…) on the GLCapabilities I pass to the pbuffer, and making a separate GLCapabilities object for the GLCanvas’s than I use for the pbuffer itself.
object PanelGL {
val profile = GLProfile.get(GLProfile.GL3)
val glCaps = new GLCapabilities(profile)
glCaps.setPBuffer(true)
val pbuffer = GLDrawableFactory.getFactory(profile).createGLPbuffer(glCaps,
new DefaultGLCapabilitiesChooser(),
1, 1, null)
}
class PanelGL extends JPanel with GLEventListener with MouseListener with MouseMotionListener {
setLayout(new BorderLayout())
setMinimumSize(new Dimension(200,200))
setPreferredSize(new Dimension(800,600))
var glCaps = new GLCapabilities(PanelGL.profile)
val canvas = new GLCanvas(glCaps, new DefaultGLCapabilitiesChooser(), PanelGL.pbuffer.getContext(), null)
add(BorderLayout.CENTER, canvas)
canvas.addGLEventListener(this)
canvas.addMouseListener(this)
canvas.addMouseMotionListener(this)