title: Fixing Severe Flickering in Window with Multiple Viewports
body: <h1>Summary</h1>
  
  <p>I have a fairly sized system where a <code>JFrame</code> with a <code>GLCanvas</code> are being used to render scenes (using OpenGL). The drawing surface of the <code>canvas</code> can be divided into several (e.g. 4) viewports. Scene objects are rendered at different times, and multiple invocations of <code>canvas.display()</code> are necessary before the contents of the entire scene have been processed.</p>
  
  <p>I've set <code>canvas.setAutoSwapBufferMode(false);</code> and manually invoke <code>canvas.swapBuffers();</code>, as per the documentation. I do that after the contents of <em>every</em> viewport have been rendered, so that the back buffer is swapped once <em>per frame</em> as opposed to once <em>per viewport</em>, which is what JOGL does automatically by default after every <code>display(GLAutoDrawable)</code> pass. (Note that the issue does not go away by simply keeping the default behavior, but there's still a need to do this manually.)</p>
  
  <p>The problem I'm running into is that I see a severe flickering effect in <strong><em>some</em></strong> OS/GPU setups. (Please see screen-shots below for examples.) I'm able to test my code in the following setups:</p>
  
  <ul>
  <li>Kubuntu 17.04 + NVIDIA GTX-960M (main dev system)</li>
  <li>Windows 10 + NVIDIA GTX-960M</li>
  <li>Kubuntu 17.04 + NVIDIA GTX-770</li>
  <li>Windows 7 + NVIDIA GTX-770M</li>
  </ul>
  
  <p>Out of those setups, only my main development system <em>appears</em> correct, but in the other setups severe flickering and/or other artifacts are observed in different ways.</p>
  
  <p>It <em>looks</em> like a back-buffer management issue, but it's not clear to me what the cause is, and I've observed no OpenGL error codes.</p>
  
  <p>I wrote a MCVE (code below) using the approach in the larger system to reproduce the issue separately. Note that I use <code>glViewport</code> and <code>glScissor</code> in order to limit the viewport area to be updated during a specific <code>display(GLAutoDrawable)</code> invocation. (Yes, <code>GL_SCISSOR_TEST</code> is enabled.)</p>
  
  <p><strong>My questions are:</strong></p>
  
  <ol>
  <li>Does buffer management in my MCVE look correct to you?</li>
  <li>What is the <em>proper</em> way to render to multiple viewports in the same window?</li>
  <li>Does <em>anything else</em> stand out as a potential cause of the problem?</li>
  </ol>
  
  <p>Thanks in advance.</p>
  
  <hr>
  
  <p>I've gone over several other questions, but their probems are different (e.g. overlapping viewports, but I'm not trying to overlap them). In addition, they're not using JOGL and its infrastructure, but mine is.</p>
  
  <p>I've also seen older questions pointing to out-dated NeHe tutorials (e.g. <a href="https://stackoverflow.com/questions/726379/multiple-viewports-in-opengl">this one</a>) but even trying out what the article proposes (i.e. clear color buffer once before rendering starts and then only the depth buffer before rendering each viewport) does not work as intended and introduces other issues outside the scope of this post.</p>
  
  <hr>
  
  <h1>MCVE Code Sample</h1>
  
  <p>This code sample requires <a href="https://jogamp.org/deployment/jogamp-current/archive/" rel="nofollow noreferrer">JOGL</a> and OpenGL 4.0 at a minimum. Feel free to copy/paste and run it locally.</p>
  
  <pre><code>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); // &lt;&lt;--- 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 &lt; viewports.length; ++i) {
              activeViewport = viewports[i];
  
              displayRequested.set(true);
              canvas.display();
              displayRequested.set(false);
          }
          canvas.swapBuffers(); // &lt;&lt;--- 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);
          }
  
      }
  
  }
  </code></pre>
  
  <hr>
  
  <h1>Screenshots</h1>
  
  <h3>Kubuntu 17.04 + GTX-960M (reference)</h3>
  
  <p><a href="https://i.stack.imgur.com/OSUSN.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/OSUSN.png" alt="Kubuntu 17.04 + GTX-960M (correct)"></a></p>
  
  <h3>Windows 10 + GTX-960M (not correct)</h3>
  
  <p>These were taken during the same run a few seconds apart after resizing the window. The issue looks different in Windows 7, but these should be enough to show the problem.</p>
  
  <p><a href="https://i.stack.imgur.com/nQmlU.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/nQmlU.png" alt="Windows 10 + GTX-960M (1)"></a></p>
  
  <p><a href="https://i.stack.imgur.com/9YcPR.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/9YcPR.png" alt="Windows 10 + GTX-960M (2)"></a></p>