【发布时间】:2017-08-24 07:00:30
【问题描述】:
总结
我有一个相当大的系统,其中 JFrame 和 GLCanvas 用于渲染场景(使用 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(下面的代码)来单独重现问题。请注意,我使用glViewport 和glScissor 来限制在特定display(GLAutoDrawable) 调用期间要更新的视口区域。 (是的,GL_SCISSOR_TEST 已启用。)
我的问题是:
- 您认为我的 MCVE 中的缓冲区管理是否正确?
- 在同一窗口中渲染到多个视口的正确方法是什么?
- 还有其他原因是问题的潜在原因吗?
提前致谢。
我已经讨论了其他几个问题,但它们的问题是不同的(例如重叠的视口,但我不想重叠它们)。此外,他们没有使用 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 中看起来有所不同,但这些应该足以显示问题。
【问题讨论】: