【问题标题】:Fixing Severe Flickering in Window with Multiple Viewports修复具有多个视口的窗口中的严重闪烁
【发布时间】:2017-08-24 07:00:30
【问题描述】:

总结

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

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

我遇到的问题是我在 一些 OS/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. 还有其他原因是问题的潜在原因吗?

提前致谢。


我已经讨论了其他几个问题,但它们的问题是不同的(例如重叠的视口,但我不想重叠它们)。此外,他们没有使用 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(参考)

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

这些是在调整窗口大小后相隔几秒钟在同一运行期间拍摄的。这个问题在 Windows 7 中看起来有所不同,但这些应该足以显示问题。

【问题讨论】:

    标签: java opengl viewport jogl


    【解决方案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();
     }
    

    【讨论】:

    • 我诚挚的歉意。这是我将代码移至 post/mcve 时的错误。显示问题的较大应用程序在循环外有 canvas.swapBuffers(); 行。 (对 swapBuffers() 的实际调用是在 AWT 事件线程中找到的,而不是在 GLEventListener 线程中。)我已经在 OP 中编辑/更正了 MCVE。对于帖子中的误报,我们深表歉意。
    • 我需要在睡一觉后回到这个话题。提前致谢。
    • @ray: "The actual call to swapBuffers() is found in the AWT Event Thread"...等等什么?你确定你正确同步了吗? OpenGL本质上是异步执行的,因此渲染函数的结束可能在渲染实际完成之前很久。
    • 当 OpenGL 上下文为当前时,使用 GLCanvas.invoke(GLRunnable) 调用你的东西。
    • @datenwolf 是的,我正在调查线程/同步问题。昨天写的时候突然想到了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-09
    • 2019-10-11
    • 2011-10-28
    • 1970-01-01
    • 1970-01-01
    • 2014-04-29
    相关资源
    最近更新 更多