使用多个视口修复窗口中的严重闪烁

时间:2017-08-24 07:00:31

标签: java opengl viewport jogl

摘要

我有一个相当大的系统,其中JFrame使用GLCanvas来渲染场景(使用OpenGL)。 canvas的绘图表面可以分为几个(例如4个)视口。场景对象在不同时间呈现,在处理整个场景的内容之前,需要多次调用canvas.display()

根据文档,我已设置canvas.setAutoSwapBufferMode(false);并手动调用canvas.swapBuffers();。我已经渲染了每个视口的内容后执行此操作,因此后台缓冲区每帧交换一次,而不是每个视口 ,这是JOGL在每次display(GLAutoDrawable)次传递后默认自动执行的操作。 (请注意,只需保留默认行为即可解决问题,但仍需要手动执行此操作。)

我遇到的问题是我在 某些 操作系统/ GPU设置中看到了严重的闪烁效果。 (请参阅下面的屏幕截图以获取示例。)我可以在以下设置中测试我的代码:

  • Kubuntu 17.04 + NVIDIA GTX-960M(主开发系统)
  • Windows 10 + NVIDIA GTX-960M
  • Kubuntu 17.04 + NVIDIA GTX-770
  • Windows 7 + NVIDIA GTX-770M

在这些设置中,只有我的主开发系统出现正确,但在其他设置中,会以不同的方式观察到严重的闪烁和/或其他伪影。

看起来就像一个后台缓冲管理问题,但我不清楚原因是什么,我没有观察到OpenGL错误代码。

我使用较大系统中的方法编写了一个MCVE(下面的代码)来单独重现该问题。请注意,我使用glViewportglScissor来限制在特定display(GLAutoDrawable)调用期间更新视口区域。 (是的,GL_SCISSOR_TEST已启用。)

我的问题是:

  1. 我的MCVE中的缓冲区管理对您来说是否正确?
  2. 在同一窗口中呈现多个视口的正确方法是什么?
  3. 其他是否会成为问题的潜在原因?
  4. 提前致谢。

    我已经讨论了其他几个问题,但他们的探测器是不同的(例如重叠的视口,但我不是试图重叠它们)。此外,他们没有使用JOGL及其基础设施,但我的是。

    我也看到过早的问题指向过时的NeHe教程(例如this one),但是甚至尝试了文章提出的内容(即在渲染开始之前清除一次颜色缓冲区,然后在渲染之前只使用深度缓冲区)每个视口)不能按预期工作,并引入了本文范围之外的其他问题。

    MCVE代码示例

    此代码示例至少需要JOGL和OpenGL 4.0。随意复制/粘贴并在本地运行。

    import java.awt.*;
    import java.awt.event.*;
    import java.nio.*;
    import java.util.*;
    import java.util.Timer;
    import java.util.concurrent.atomic.*;
    
    import javax.swing.*;
    
    import com.jogamp.opengl.*;
    import com.jogamp.opengl.awt.*;
    import com.jogamp.opengl.util.glsl.*;
    
    public class ManualViewportBufferClearingTest implements GLEventListener, KeyListener {
    
        private Timer                renderLoopTimer  = new Timer();
        private JFrame               frame            = new JFrame(ManualViewportBufferClearingTest.class.getName());
        private GLCanvas             canvas;
    
        private ShaderProgram        shaderProgram;
    
        private int[]                vaos             = new int[1];
        private int[]                vbos             = new int[2];
    
        private Viewport[]           viewports;
        private Viewport             activeViewport;
    
        /**
         * Avoid performing display logic (e.g. automatically on initialization) unless
         * the client has explicitly requested it.
         *
         * This is set/unset in the AWT Event Thread, but checked in the GLEventListener
         * Thread.
         */
        private AtomicBoolean        displayRequested = new AtomicBoolean(false);
    
        // @formatter:off
        private static final float[] vertexPositions    = new float[] {
             .25f,  .25f, 0f, 1f,
            -.25f, -.25f, 0f, 1f,
             .25f, -.25f, 0f, 1f
        };
        private static final float[] vertexColors       = new float[] {
            1f, 1f, 1f, 1f,
            1f, 1f, 1f, 1f,
            1f, 1f, 1f, 1f
        };
        // @formatter:on
    
        private FloatBuffer          vertices         = FloatBuffer.wrap(vertexPositions);
        private FloatBuffer          offsets          = FloatBuffer.wrap(new float[] { 0, 0, 0, 0 });
        private FloatBuffer          colors           = FloatBuffer.wrap(vertexColors);
    
        public ManualViewportBufferClearingTest() {
            final GLCapabilities caps = new GLCapabilities(GLProfile.get(GLProfile.GL4));
            caps.setBackgroundOpaque(true);
            caps.setDoubleBuffered(true);
            caps.setRedBits(8);
            caps.setGreenBits(8);
            caps.setBlueBits(8);
            caps.setAlphaBits(8);
    
            canvas = new GLCanvas(caps);
            canvas.addGLEventListener(this);
            canvas.addKeyListener(this);
            canvas.setAutoSwapBufferMode(false); // <<--- IMPORTANT!! See Manual Swapping Later.
    
            final int pixelWidth = 1024;
            final int pixelHeight = 768;
    
            frame.setSize(pixelWidth, pixelHeight);
            frame.setLocationRelativeTo(null);
            frame.getContentPane().setLayout(new BorderLayout());
            frame.getContentPane().add(canvas, BorderLayout.CENTER);
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    
            resetViewports(pixelWidth, pixelHeight);
    
            frame.setVisible(true);
        }
    
        @Override
        public void init(GLAutoDrawable glad) {
            GL4 gl = (GL4) glad.getGL();
    
            gl.glEnable(GL4.GL_DEPTH_TEST);
            gl.glEnable(GL4.GL_SCISSOR_TEST);
    
            gl.glGenVertexArrays(vaos.length, vaos, 0);
            gl.glBindVertexArray(vaos[0]);
    
            setupBuffers(gl);
            buildProgram(gl);
    
            shaderProgram.useProgram(gl, true);
    
            renderLoopTimer.scheduleAtFixedRate(new TimerTask() {
    
                @Override
                public void run() {
                    renderToViewports();
                }
            }, 0, 16); // draw every 16ms, for 60 FPS
        }
    
        @Override
        public void display(GLAutoDrawable glad) {
            if (!displayRequested.get())
                return;
    
            // apply a simple animation
            final double value = System.currentTimeMillis() / 503.0;
            offsets.put(0, (float) (Math.sin(value) * 0.5));
            offsets.put(1, (float) (Math.cos(value) * 0.6));
    
            GL4 gl = (GL4) glad.getGL();
    
            gl.glViewport(activeViewport.x, activeViewport.y, activeViewport.width, activeViewport.height);
            gl.glScissor(activeViewport.x, activeViewport.y, activeViewport.width, activeViewport.height);
    
            gl.glClearBufferfv(GL4.GL_COLOR, 0, activeViewport.colorBuffer);
            gl.glClearBufferfv(GL4.GL_DEPTH, 0, activeViewport.depthBuffer);
    
            gl.glVertexAttrib4fv(/* layout (location = */1, offsets);
            gl.glDrawArrays(GL4.GL_TRIANGLES, 0, 3);
        }
    
        @Override
        public void dispose(GLAutoDrawable glad) {
            GL4 gl = (GL4) glad.getGL();
            shaderProgram.destroy(gl);
            gl.glDeleteVertexArrays(vaos.length, vaos, 0);
            gl.glDeleteBuffers(vbos.length, vbos, 0);
        }
    
        @Override
        public void reshape(GLAutoDrawable glad, int x, int y, int width, int height) {
            GL4 gl = glad.getGL().getGL4();
    
            resetViewports(width, height);
    
            Viewport vp = viewports[0];
            gl.glViewport(vp.x, vp.y, vp.width, vp.height);
            gl.glScissor(vp.x, vp.y, vp.width, vp.height);
        }
    
        @Override
        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_ESCAPE:
                    cleanup();
                    frame.dispose();
                    System.exit(0);
                    break;
            }
        }
    
        private void setupBuffers(GL4 gl) {
            gl.glGenBuffers(vbos.length, vbos, 0);
    
            gl.glBindBuffer(GL4.GL_ARRAY_BUFFER, vbos[0]);
            gl.glBufferData(GL4.GL_ARRAY_BUFFER, vertices.capacity() * Float.BYTES, vertices, GL4.GL_STATIC_DRAW);
            gl.glVertexAttribPointer(/* layout (location = */ 0, /* vec4 = */ 4 /* floats */, GL4.GL_FLOAT, false, 0, 0);
            gl.glEnableVertexAttribArray(0);
    
            gl.glBindBuffer(GL4.GL_ARRAY_BUFFER, vbos[1]);
            gl.glBufferData(GL4.GL_ARRAY_BUFFER, colors.capacity() * Float.BYTES, colors, GL4.GL_STATIC_DRAW);
            gl.glVertexAttribPointer(/* layout (location = */ 2, /* vec4 = */ 4 /* floats */, GL4.GL_FLOAT, false, 0, 0);
            gl.glEnableVertexAttribArray(2);
        }
    
        private void resetViewports(int width, int height) {
            final int halfW = width / 2;
            final int halfH = height / 2;
    
            // @formatter:off
            viewports = new Viewport[] {
                new Viewport(0    , 0    , halfW, halfH, Color.BLUE),   // bot left
                new Viewport(halfW, 0    , halfW, halfH, Color.GRAY),   // bot right
                new Viewport(0    , halfH, halfW, halfH, Color.RED),    // top left
                new Viewport(halfW, halfH, halfW, halfH, Color.GREEN)   // top right
            };
            // @formatter:on
        }
    
        private void renderToViewports() {
            for (int i = 0; i < viewports.length; ++i) {
                activeViewport = viewports[i];
    
                displayRequested.set(true);
                canvas.display();
                displayRequested.set(false);
            }
            canvas.swapBuffers(); // <<--- MANUAL SWAP REQUIRED; See canvas.setAutoSwapBufferMode(false)!!
        }
    
        private void cleanup() {
            renderLoopTimer.cancel();
            canvas.disposeGLEventListener(this, true);
    
            vertices.clear();
            offsets.clear();
            colors.clear();
    
            viewports = null;
            activeViewport = null;
            vertices = null;
            offsets = null;
            colors = null;
        }
    
        private static String getVertexSource() {
            // @formatter:off
            return
                "#version 400 core                                      \n"
                + "                                                     \n"
                + "layout (location = 0) in vec4 vertex_position;       \n"
                + "layout (location = 1) in vec4 vertex_offset;         \n"
                + "layout (location = 2) in vec4 vertex_color;          \n"
                + "                                                     \n"
                + "out vertex_t {                                       \n"
                + "    vec4 color;                                      \n"
                + "} vs;                                                \n"
                + "                                                     \n"
                + "void main() {                                        \n"
                + "    vs.color      = vertex_color;                    \n"
                + "    gl_Position   = vertex_position + vertex_offset; \n"
                + "}                                                    \n";
            // @formatter:on
        }
    
        private static String getFragmentSource() {
            // @formatter:off
            return
                "#version 400 core                                      \n"
                + "                                                     \n"
                + "in vertex_t {                                        \n"
                + "    vec4 color;                                      \n"
                + "} fs;                                                \n"
                + "                                                     \n"
                + "out vec4 fragment;                                   \n"
                + "                                                     \n"
                + "void main() {                                        \n"
                + "    fragment = fs.color;                             \n"
                + "}                                                    \n";
            // @formatter:on
        }
    
        private void buildProgram(GL4 gl) {
            shaderProgram = new ShaderProgram();
    
            ShaderCode vs = createShader(gl, GL4.GL_VERTEX_SHADER, getVertexSource());
            ShaderCode fs = createShader(gl, GL4.GL_FRAGMENT_SHADER, getFragmentSource());
    
            shaderProgram.init(gl);
            shaderProgram.add(vs);
            shaderProgram.add(fs);
    
            shaderProgram.link(gl, System.err);
            if (!shaderProgram.validateProgram(gl, System.err))
                throw new RuntimeException("Program failed to link");
    
            vs.destroy(gl);
            fs.destroy(gl);
        }
    
        private ShaderCode createShader(GL4 gl, int shaderType, String source) {
            String[][] sources = new String[1][1];
            sources[0] = new String[] { source };
    
            ShaderCode shader = new ShaderCode(shaderType, sources.length, sources);
    
            if (!shader.compile(gl, System.err))
                throw new RuntimeException("Shader compilation failed\n" + source);
    
            return shader;
        }
    
        @Override
        public void keyReleased(KeyEvent e) {}
    
        @Override
        public void keyTyped(KeyEvent e) {}
    
        public static void main(String[] args) {
            new ManualViewportBufferClearingTest();
        }
    
        /**
         * Utility class for a window viewport.
         */
        private class Viewport {
    
            public int         x, y;
            public int         width, height;
    
            public FloatBuffer colorBuffer;
            public FloatBuffer depthBuffer;
    
            public Viewport(int x, int y, int width, int height, Color color, float depth) {
                this.x = x;
                this.y = y;
                this.width = width;
                this.height = height;
                this.depthBuffer = FloatBuffer.wrap(new float[] { depth });
    
                float[] components = color.getColorComponents(null);
                colorBuffer = FloatBuffer.wrap(new float[] { components[0], components[1], components[2], 0 });
            }
    
            public Viewport(int x, int y, int width, int height, Color color) {
                this(x, y, width, height, color, 1f);
            }
    
        }
    
    }
    

    截图

    Kubuntu 17.04 + GTX-960M(参考)

    Kubuntu 17.04 + GTX-960M (correct)

    Windows 10 + GTX-960M(不正确)

    这些是在调整窗口大小后几秒钟内在相同的运行期间拍摄的。 Windows 7中的问题看起来有所不同,但这些问题足以说明问题。

    Windows 10 + GTX-960M (1)

    Windows 10 + GTX-960M (2)

1 个答案:

答案 0 :(得分:1)

您的问题就在这里:您在渲染每个视口后重新交换缓冲区。

private void renderToViewports() {
    for (int i = 0; i < viewports.length; ++i) {
        activeViewport = viewports[i];

        displayRequested.set(true);
        canvas.display();
        displayRequested.set(false);

        canvas.swapBuffers();
    }
}

当然,这种方式会闪烁。

将其改为此,你应该做得很好:

 private void renderToViewports() {
     for (int i = 0; i < viewports.length; ++i) {
        /* ... */

        // <<<< remove buffer swap here and... 
     }

     // >>>>> move it here!

     canvas.swapBuffers();
 }