Fix serious flickering in a window with multiple viewports

Summary

I have a fairly dimensional system where JFramec is GLCanvasused to render scenes (using OpenGL). The drawing surface canvascan be divided into several (for example, 4) viewports. Scene objects are displayed at different times, and several requests are canvas.display()needed before the contents of the entire scene are processed.

I installed canvas.setAutoSwapBufferMode(false);and manually call canvas.swapBuffers();, according to the documentation. I do this after the contents of each viewport have been displayed, so that the back buffer is replaced once per frame, and not once per viewport, which makes JOGL automatically by default after each pass display(GLAutoDrawable). (Note that the problem does not go away, just keeping the default behavior, but still you need to do it manually.)

The problem I am facing is that I see a strong flickering effect in some OS / GPU settings . (For an example, see the Screenshots below.) I can check my code in the following settings:

  • Kubuntu 17.04 + NVIDIA GTX-960M (dev main system)
  • Windows 10 + NVIDIA GTX-960M
  • Kubuntu 17.04 + NVIDIA GTX-770
  • Windows 7 + NVIDIA GTX-770M

, / -.

, , , OpenGL.

MCVE ( ), , . , glViewport glScissor, , display(GLAutoDrawable). (, GL_SCISSOR_TEST .)

:

  • MCVE?
  • ?
  • - ?

.


, (, , ). , JOGL , .

, NeHe (, ), , (.. , ) , .


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);

        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();
    }

    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)

+4
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();
 }
0

Source: https://habr.com/ru/post/1684350/


All Articles