Sharing context between drawables issues.

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Sharing context between drawables issues.

ZooChar
Hello,

I'm working on an SWT, non-gaming 3D application. I have a standard view which works fine, using the SWT_AWT bridge. One of the features that were requested of me require creating some sort of a thumbnail output, so using an offscreen drawable seems ideal. However, I'm running into multiple strange issues.

This is how I declare the offscreen drawable:
GLCapabilities caps = new GLCapabilities(shared.profile);
caps.setOnscreen(false);
caps.setHardwareAccelerated(true);
caps.setDoubleBuffered(false);
caps.setBackgroundOpaque(false);
GLDrawableFactory factory = GLDrawableFactory.getFactory(shared.profile);
drawable = factory.createOffscreenAutoDrawable(factory.getDefaultDevice(), caps, null, width, height);
drawable.setSharedContext(shared.context);

shared is just a class that I pass through the constructor. I tried various changes in the above, but they don't seem to make a difference (for instance, I tried also passing the same capabilities).

Now, I use this code to create an output image (same class):
public BufferedImage renderImage() {
	drawable.display();
	AWTGLReadBufferUtil util = new AWTGLReadBufferUtil(drawable.getGLProfile(), true);
	image = util.readPixelsToBufferedImage(drawable.getGL().getGL3(), 0, 0, width, height, true);
	return image;
}

And this seems to work fine, however, I get this in the console:
Info: GLReadBufferUtil.readPixels: pre-exisiting GL error 0x502

Using the debugging tools it seems that I get this error whenever I use anything that was bound to the GL3 object in the main view, like for example binding a buffer or a texture. The actual GL3 object in the offscreen drawable seems to be a different instance than the one in the standard 3D view. I don't know if it's supposed to be this way or not, or if the actual GL3 objects have to be identical.

So, here's another issue that I'm having that complicates things. I have this code in a different place (in a SWT thread):

ThumbnailFactory factory = new ThumbnailFactory(session, scene.getSharedData(), 240, 240);
BufferedImage image = factory.renderImage();

Calling this code a SECOND time does not display the previous error, however, the output does not change at all. No matter what I do, I always get the same image from renderImage(). This is very strange, especially considering everything is a different instance.

To fix this second issue, I simply keep a reference to the first ThumbnailFactory class and reuse it. I really don't mind doing that (it's a good idea anyway), but I need to understand why this is happening and what I can do to address it.

Thank you for your time!
Reply | Threaded
Open this post in threaded view
|

Re: Sharing context between drawables issues.

gouessej
Administrator
Hi

Please provide a SSCCE as it's difficult to find the culprit only with a few pieces of code.
Julien Gouesse | Personal blog | Website
Reply | Threaded
Open this post in threaded view
|

Re: Sharing context between drawables issues.

ZooChar
This post was updated on .
The code I've written so far is very large and mature (e.g. it has big Mesh, Shader, Effect etc classes), and I am not allowed to share information about it. However, I created a much simpler example that exhibits a similar behaviour. I used the code from here to draw the triangle: http://stackoverflow.com/questions/21820644/opengl-3-2-trying-to-render-a-triangle-with-shaders-but-output-is-distorted (no need to visit the link, just giving credit).

EDIT: I managed to fix one of the two issues. I noted it in the code now.

In contrast to the previous code, I use this simple example class now to create a master context:

GLUtil

package jogltest;

import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLContext;
import com.jogamp.opengl.GLDrawableFactory;
import com.jogamp.opengl.GLProfile;

public class GLUtil {
    private static GLAutoDrawable sharedDrawable;
    
    public static GLContext getSharedContext() {
        if (sharedDrawable == null) {
            GLProfile profile = GLProfile.get(GLProfile.GL3);
            GLCapabilities capabilities = new GLCapabilities(profile);
            sharedDrawable = GLDrawableFactory.getFactory(profile).createDummyAutoDrawable(null, true, capabilities, null);
            sharedDrawable.display();
        }
        
        return sharedDrawable.getContext();
    }
}

The following is a class that works just fine:

EditorComposite

package jogltest;

import java.awt.Frame;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import org.eclipse.swt.SWT;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.widgets.Composite;

import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL3;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.awt.GLCanvas;

public class EditorComposite extends Composite {
    
    private EditorSceneListener listener;
    
    // Keeping these public for the sake of simplicity.
    public int vertexShader;
    public int fragmentShader;
    public int program;
    public int positionAttribute;
    public int vao;
    public int vbo;

    public EditorComposite(Composite parent, int style) {
        super(parent, SWT.EMBEDDED);

        GLProfile profile = GLProfile.get(GLProfile.GL3);
        GLCapabilities capabilities = new GLCapabilities(profile);
        GLCanvas canvas = new GLCanvas(capabilities);
        canvas.setSharedContext(GLUtil.getSharedContext());

        Frame frame = SWT_AWT.new_Frame(this);
        frame.add(canvas);

        listener = new EditorSceneListener();

        canvas.addGLEventListener(listener);
        canvas.display();
    }

    public class EditorSceneListener implements GLEventListener {

        private String[] vertProgram = new String[] {
            "#version 330\n" +
            "in vec2 position;\n" +
            "void main() {\n" +
            "    gl_Position = vec4(position, 0.0, 1.0);\n" +
            "}\n"};
        
        private String[] fragProgram = new String[] {
            "#version 330\n" +
            "out vec4 outColor;\n" +
            "void main() {\n" +
            "    outColor = vec4(1.0, 1.0, 1.0, 1.0);\n" +
            "}\n"};

        private FloatBuffer vertBuffer;

        @Override
        public void display(GLAutoDrawable drawable) {
            drawable.getContext().makeCurrent();
            GL3 gl = drawable.getGL().getGL3();
            gl.setSwapInterval(1);
            gl.glViewport(0, 0, 300, 300);
            gl.glDepthFunc(GL3.GL_LEQUAL);

            gl.glClearDepth(1.0f);
            gl.glClear(GL3.GL_COLOR_BUFFER_BIT | GL3.GL_DEPTH_BUFFER_BIT);
            gl.glClearColor(0f, 0f, 0f, 1f);
            
            gl.glUseProgram(program);
            gl.glBindVertexArray(vao);
            gl.glBindBuffer(GL3.GL_ARRAY_BUFFER, vbo);
            gl.glEnableVertexAttribArray(positionAttribute);
            gl.glVertexAttribPointer(positionAttribute, 2, GL.GL_FLOAT, false, 0, 0L);
            gl.glDrawArrays(GL.GL_TRIANGLES, 0, 3);

        }

        @Override
        public void dispose(GLAutoDrawable drawable) {
        }

        @Override
        public void init(GLAutoDrawable drawable) {
            drawable.getContext().makeCurrent();
            
            float[] verts = { 0.0f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f };

            IntBuffer vaoBuffer = Buffers.newDirectIntBuffer(1);

            GL3 gl = drawable.getGL().getGL3();
            gl.glGenVertexArrays(1, vaoBuffer);
            vao = vaoBuffer.get(0);
            gl.glBindVertexArray(vao);

            IntBuffer vboBuffer = Buffers.newDirectIntBuffer(1);
            vertBuffer = Buffers.newDirectFloatBuffer(verts);

            gl.glGenBuffers(1, vboBuffer);
            vbo = vboBuffer.get(0);

            gl.glBindBuffer(GL3.GL_ARRAY_BUFFER, vbo);
            gl.glBufferData(GL3.GL_ARRAY_BUFFER, vertBuffer.limit() * Buffers.SIZEOF_FLOAT, vertBuffer, GL3.GL_STATIC_DRAW);

            vertexShader = gl.glCreateShader(GL3.GL_VERTEX_SHADER);
            gl.glShaderSource(vertexShader, 1, vertProgram, null);
            gl.glCompileShader(vertexShader);

            fragmentShader = gl.glCreateShader(GL3.GL_FRAGMENT_SHADER);
            gl.glShaderSource(fragmentShader, 1, fragProgram, null);
            gl.glCompileShader(fragmentShader);

            program = gl.glCreateProgram();
            gl.glAttachShader(program, vertexShader);
            gl.glAttachShader(program, fragmentShader);
            gl.glBindFragDataLocation(program, 0, "outColor");
            gl.glLinkProgram(program);

            gl.glUseProgram(program);

            positionAttribute = gl.glGetAttribLocation(program, "position");
        }

        @Override
        public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {
        }
    }
}


The following is the OffscreenRenderer which is supposed to share the same buffers and everything. One difference is that it displays a different background color (to test that the output changed). I'm having the following issues with it:
1) I get the following error: "Info: GLReadBufferUtil.readPixels: pre-exisiting GL error 0x502", though I do get an output.
2) (FIXED) If I create another offscreen drawable, it doesn't work. I either get a blank image or the image output of the very first offscreen drawable (it's not random - it apparently swaps between the two each time).

OffscreenRednerer

package jogltest;

import java.awt.image.BufferedImage;

import com.jogamp.opengl.DefaultGLCapabilitiesChooser;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL3;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLDrawableFactory;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLOffscreenAutoDrawable;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.util.awt.AWTGLReadBufferUtil;

public class OffscreenRenderer {
    
    private float bgRed;
    private float bgGreen;
    private float bgBlue;
    private GLOffscreenAutoDrawable offScreenDrawable;
    private OffscreenListener listener;
    
    private EditorComposite parent;
    
    public void setBackground(float r, float g, float b) {
        bgRed = r; 
        bgGreen = g; 
        bgBlue = b;
    }

    public OffscreenRenderer(EditorComposite parent) {
        this.parent = parent;
        
        GLProfile profile = GLProfile.get(GLProfile.GL3);
        GLCapabilities caps = new GLCapabilities(profile);
        caps.setOnscreen(false);
        caps.setHardwareAccelerated(true);
        caps.setDoubleBuffered(true);
        caps.setBackgroundOpaque(false);
        GLDrawableFactory factory = GLDrawableFactory.getFactory(profile);
        
        offScreenDrawable = factory.createOffscreenAutoDrawable(factory.getDefaultDevice(), caps, new DefaultGLCapabilitiesChooser(), 300, 300);
        offScreenDrawable.setSharedContext(GLUtil.getSharedContext());

        listener = new OffscreenListener();
        offScreenDrawable.addGLEventListener(listener);
    }

    public BufferedImage renderImage() {
        offScreenDrawable.display();
        AWTGLReadBufferUtil util = new AWTGLReadBufferUtil(offScreenDrawable.getGLProfile(), true);
        BufferedImage image = util.readPixelsToBufferedImage(offScreenDrawable.getGL().getGL3(), 0, 0, 300, 300, true);
        
        //NOTE: This is the fix I added for issue (2). Releasing the context after drawing allows for more drawables to be created.
        offScreenDrawable.getContext().release();

        return image;
    }
    
    private class OffscreenListener implements GLEventListener {

        @Override
        public void display(GLAutoDrawable drawable) {
            drawable.getContext().makeCurrent();
            
            GL3 gl = drawable.getGL().getGL3();
            
            gl.setSwapInterval(1);
            gl.glViewport(0, 0, 300, 300);
            gl.glDepthFunc(GL3.GL_LEQUAL);

            gl.glClearDepth(1.0f);
            gl.glClearColor(bgRed, bgGreen, bgBlue, 1f);
            gl.glClear(GL3.GL_COLOR_BUFFER_BIT | GL3.GL_DEPTH_BUFFER_BIT);
            
            gl.glUseProgram(parent.program);
            gl.glBindVertexArray(parent.vao);
            gl.glBindBuffer(GL3.GL_ARRAY_BUFFER, parent.vbo);
            gl.glEnableVertexAttribArray(parent.positionAttribute);
            gl.glVertexAttribPointer(parent.positionAttribute, 2, GL.GL_FLOAT, false, 0, 0L);

            gl.glDrawArrays(GL.GL_TRIANGLES, 0, 3);
        }

        @Override
        public void dispose(GLAutoDrawable drawable) { }
        @Override
        public void init(GLAutoDrawable drawable) { }

        @Override
        public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) { }
    }

}

Now, here's the code that binds everything together. It creates a window with a GLCanvas on the left, and a panel on the right. By pressing "Render", a Color Dialog opens up. After you select a color, a BufferedImage is created and drawn on the right panel using the selected color as a background.

DemoWindow

package jogltest;

import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Panel;
import java.awt.image.BufferedImage;

import org.eclipse.swt.SWT;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class DemoWindow {

    protected Shell shell;

    public static void main(String[] args) {
        try {
            DemoWindow window = new DemoWindow();
            window.open();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void open() {
        Display display = Display.getDefault();
        createContents();
        shell.open();
        shell.layout();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }
    
    private static class ImagePanel extends Panel {
        private static final long serialVersionUID = 1049213893091559571L;
        
        private BufferedImage image;
        
        public void setImage(BufferedImage image) {
            this.image = image;
            this.repaint();
        }
        
        @Override
        public void paint(Graphics g) {
            g.drawImage(image, 0, 0, this.getWidth(), this.getHeight(), this);
        }   
    }

    private RGB background = new RGB(0, 0, 0);
    private ImagePanel rightPanel;
    private OffscreenRenderer renderer;

    protected void createContents() {
        shell = new Shell();
        shell.setSize(640, 384);
        shell.setText("SWT Application");
        
        EditorComposite left = new EditorComposite(shell, SWT.EMBEDDED);
        left.setBounds(10, 10, 300, 300);
        
        Button btnNewButton = new Button(shell, SWT.NONE);
        btnNewButton.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                ColorDialog dlg = new ColorDialog(shell);
                dlg.setRGB(background);
                RGB rgb = dlg.open();
                if (rgb != null) {
                    background = rgb;
                }
                
                if (renderer == null) {
                    renderer = new OffscreenRenderer(left);
                }
                
                renderer.setBackground(background.red / 255f, background.green / 255f, background.blue / 255f);
                                
                BufferedImage image = renderer.renderImage();
                
                // Uncommenting the following used to mess everything up, but not anymore (after fixing Issue #2)
                //renderer = null;
                
                rightPanel.setImage(image);
            }
        });
        btnNewButton.setBounds(559, 316, 57, 25);
        btnNewButton.setText("Render");
        
        Composite right = new Composite(shell, SWT.EMBEDDED);
        right.setBounds(316, 10, 300, 300);
        
        Frame frame = SWT_AWT.new_Frame(right);
        
        rightPanel = new ImagePanel();
        frame.add(rightPanel);
    }
}
Reply | Threaded
Open this post in threaded view
|

Re: Sharing context between drawables issues.

gouessej
Administrator
Enable the debug logs, it will tell you which line throws the GLException matching with this error code.
Julien Gouesse | Personal blog | Website
Reply | Threaded
Open this post in threaded view
|

Re: Sharing context between drawables issues.

ZooChar
After fixing all the issues, it seems that the problem is now the sharing of VAOs. After looking it up, I found out that VAOs are not meant to be shared, which is something I didn't know. If I don't use VAOs, everything works just fine.

It turns out that in my original application I was using both VAOs and rebinding VBOs every time (making the use of VAOs obsolete. Shame on me). After modifying my code to use VAOs correctly, the shared context does not display anything at all. Using only VBOs creates a valid output and does not produce any errors.

I could create a VAO for each mesh and for each context seperately, I suppose, and still share VBOs. But I think it'd be simpler to just create a single dummy VAO per context and keep it bound. Is this a good idea, or will it create too much of an overhead (assuming I don't have a lot of meshes on the screen)?
Reply | Threaded
Open this post in threaded view
|

Re: Sharing context between drawables issues.

Sven Gothel
Administrator
On 11/06/2015 11:09 AM, ZooChar [via jogamp] wrote:
>
> I could create a VAO for each mesh and for each context seperately, I suppose,
> and still share VBOs. But I think it'd be simpler to just create a single
> dummy VAO per context and keep it bound. Is this a good idea, or will it
> create too much of an overhead (assuming I don't have a lot of meshes on the
> screen)?

We already do this:
<http://jogamp.org/deployment/jogamp-next/javadoc/jogl/javadoc/com/jogamp/opengl/GLContext.html#getDefaultVAO%28%29>




signature.asc (828 bytes) Download Attachment