diff --git a/CHANGES b/CHANGES
index 2b23dbbd316..d324c1bf283 100644
--- a/CHANGES
+++ b/CHANGES
@@ -8,6 +8,7 @@
- Fixed Timer#stop, remember time spent stopped and delay tasks when started again. #7281
- Android: Add configuration option to render under the cutout if available on the device.
- Fix: Keep SelectBox popup from extending past right edge of stage.
+- Added Framebuffer multisample support (see GL31FrameBufferMultisampleTest.java for basic usage)
[1.12.1]
- LWJGL3 Improvement: Audio device is automatically switched if it was changed in the operating system.
diff --git a/gdx/src/com/badlogic/gdx/graphics/glutils/GLFrameBuffer.java b/gdx/src/com/badlogic/gdx/graphics/glutils/GLFrameBuffer.java
index bfe4241f085..75208b07310 100644
--- a/gdx/src/com/badlogic/gdx/graphics/glutils/GLFrameBuffer.java
+++ b/gdx/src/com/badlogic/gdx/graphics/glutils/GLFrameBuffer.java
@@ -28,12 +28,14 @@
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
+import com.badlogic.gdx.graphics.GL31;
import com.badlogic.gdx.graphics.GLTexture;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.BufferUtils;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.GdxRuntimeException;
+import com.badlogic.gdx.utils.IntArray;
/**
*
@@ -76,12 +78,16 @@ public abstract class GLFrameBuffer implements Disposable {
protected int depthStencilPackedBufferHandle;
/** if has depth stencil packed buffer **/
protected boolean hasDepthStencilPackedBuffer;
+ /** the colorbuffer render object handles **/
+ protected final IntArray colorBufferHandles = new IntArray();
/** if multiple texture attachments are present **/
protected boolean isMRT;
protected GLFrameBufferBuilder extends GLFrameBuffer> bufferBuilder;
+ private IntBuffer defaultDrawBuffers;
+
GLFrameBuffer () {
}
@@ -136,33 +142,48 @@ protected void build () {
if (bufferBuilder.hasDepthRenderBuffer) {
depthbufferHandle = gl.glGenRenderbuffer();
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, depthbufferHandle);
- gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, bufferBuilder.depthRenderBufferSpec.internalFormat, width, height);
+ if (bufferBuilder.samples > 0) {
+ Gdx.gl31.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER, bufferBuilder.samples,
+ bufferBuilder.depthRenderBufferSpec.internalFormat, width, height);
+ } else {
+ gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, bufferBuilder.depthRenderBufferSpec.internalFormat, width, height);
+ }
}
if (bufferBuilder.hasStencilRenderBuffer) {
stencilbufferHandle = gl.glGenRenderbuffer();
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, stencilbufferHandle);
- gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, bufferBuilder.stencilRenderBufferSpec.internalFormat, width, height);
+ if (bufferBuilder.samples > 0) {
+ Gdx.gl31.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER, bufferBuilder.samples,
+ bufferBuilder.stencilRenderBufferSpec.internalFormat, width, height);
+ } else {
+ gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, bufferBuilder.stencilRenderBufferSpec.internalFormat, width, height);
+ }
}
if (bufferBuilder.hasPackedStencilDepthRenderBuffer) {
depthStencilPackedBufferHandle = gl.glGenRenderbuffer();
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, depthStencilPackedBufferHandle);
- gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, bufferBuilder.packedStencilDepthRenderBufferSpec.internalFormat, width,
- height);
+ if (bufferBuilder.samples > 0) {
+ Gdx.gl31.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER, bufferBuilder.samples,
+ bufferBuilder.packedStencilDepthRenderBufferSpec.internalFormat, width, height);
+ } else {
+ gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, bufferBuilder.packedStencilDepthRenderBufferSpec.internalFormat, width,
+ height);
+ }
hasDepthStencilPackedBuffer = true;
}
isMRT = bufferBuilder.textureAttachmentSpecs.size > 1;
- int colorTextureCounter = 0;
+ int colorAttachmentCounter = 0;
if (isMRT) {
for (FrameBufferTextureAttachmentSpec attachmentSpec : bufferBuilder.textureAttachmentSpecs) {
T texture = createTexture(attachmentSpec);
textureAttachments.add(texture);
if (attachmentSpec.isColorTexture()) {
- gl.glFramebufferTexture2D(GL20.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0 + colorTextureCounter, GL30.GL_TEXTURE_2D,
- texture.getTextureObjectHandle(), 0);
- colorTextureCounter++;
+ gl.glFramebufferTexture2D(GL20.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0 + colorAttachmentCounter,
+ GL30.GL_TEXTURE_2D, texture.getTextureObjectHandle(), 0);
+ colorAttachmentCounter++;
} else if (attachmentSpec.isDepth) {
gl.glFramebufferTexture2D(GL20.GL_FRAMEBUFFER, GL20.GL_DEPTH_ATTACHMENT, GL20.GL_TEXTURE_2D,
texture.getTextureObjectHandle(), 0);
@@ -171,20 +192,35 @@ protected void build () {
texture.getTextureObjectHandle(), 0);
}
}
- } else {
+ } else if (bufferBuilder.textureAttachmentSpecs.size > 0) {
T texture = createTexture(bufferBuilder.textureAttachmentSpecs.first());
textureAttachments.add(texture);
gl.glBindTexture(texture.glTarget, texture.getTextureObjectHandle());
}
- if (isMRT) {
- IntBuffer buffer = BufferUtils.newIntBuffer(colorTextureCounter);
- for (int i = 0; i < colorTextureCounter; i++) {
- buffer.put(GL30.GL_COLOR_ATTACHMENT0 + i);
+ for (FrameBufferRenderBufferAttachmentSpec colorBufferSpec : bufferBuilder.colorRenderBufferSpecs) {
+ int colorbufferHandle = gl.glGenRenderbuffer();
+ gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, colorbufferHandle);
+ if (bufferBuilder.samples > 0) {
+ Gdx.gl31.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER, bufferBuilder.samples, colorBufferSpec.internalFormat,
+ width, height);
+ } else {
+ gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, colorBufferSpec.internalFormat, width, height);
}
- ((Buffer)buffer).position(0);
- Gdx.gl30.glDrawBuffers(colorTextureCounter, buffer);
- } else {
+ Gdx.gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_COLOR_ATTACHMENT0 + colorAttachmentCounter,
+ GL20.GL_RENDERBUFFER, colorbufferHandle);
+ colorBufferHandles.add(colorbufferHandle);
+ colorAttachmentCounter++;
+ }
+
+ if (isMRT || bufferBuilder.samples > 0) {
+ defaultDrawBuffers = BufferUtils.newIntBuffer(colorAttachmentCounter);
+ for (int i = 0; i < colorAttachmentCounter; i++) {
+ defaultDrawBuffers.put(GL30.GL_COLOR_ATTACHMENT0 + i);
+ }
+ ((Buffer)defaultDrawBuffers).position(0);
+ Gdx.gl30.glDrawBuffers(colorAttachmentCounter, defaultDrawBuffers);
+ } else if (bufferBuilder.textureAttachmentSpecs.size > 0) {
attachFrameBufferColorTexture(textureAttachments.first());
}
@@ -227,7 +263,12 @@ protected void build () {
depthStencilPackedBufferHandle = gl.glGenRenderbuffer();
hasDepthStencilPackedBuffer = true;
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, depthStencilPackedBufferHandle);
- gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, width, height);
+ if (bufferBuilder.samples > 0) {
+ Gdx.gl31.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER, bufferBuilder.samples, GL_DEPTH24_STENCIL8_OES, width,
+ height);
+ } else {
+ gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, width, height);
+ }
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, 0);
gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_DEPTH_ATTACHMENT, GL20.GL_RENDERBUFFER,
@@ -261,6 +302,8 @@ protected void build () {
throw new IllegalStateException("Frame buffer couldn't be constructed: missing attachment");
if (result == GL20.GL_FRAMEBUFFER_UNSUPPORTED)
throw new IllegalStateException("Frame buffer couldn't be constructed: unsupported combination of formats");
+ if (result == GL31.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
+ throw new IllegalStateException("Frame buffer couldn't be constructed: multisample mismatch");
throw new IllegalStateException("Frame buffer couldn't be constructed: unknown error " + result);
}
@@ -268,6 +311,14 @@ protected void build () {
}
private void checkValidBuilder () {
+
+ if (bufferBuilder.samples > 0 && !Gdx.graphics.isGL31Available()) {
+ throw new GdxRuntimeException("Framebuffer multisample requires GLES 3.1+");
+ }
+ if (bufferBuilder.samples > 0 && bufferBuilder.textureAttachmentSpecs.size > 0) {
+ throw new GdxRuntimeException("Framebuffer multisample with texture attachments not yet supported");
+ }
+
boolean runningGL30 = Gdx.graphics.isGL30Available();
if (!runningGL30) {
@@ -350,6 +401,78 @@ public void end (int x, int y, int width, int height) {
Gdx.gl20.glViewport(x, y, width, height);
}
+ static final IntBuffer singleInt = BufferUtils.newIntBuffer(1);
+
+ /** Transfer pixels from this frame buffer to the destination frame buffer. Usually used when using multisample, it resolves
+ * samples from this multisample FBO to a non-multisample as destination in order to be used as textures. This is a convenient
+ * method that automatically choose which of stencil, depth, and colors buffers attachment to be copied.
+ * @param destination the destination of the copy. */
+ public void transfer (GLFrameBuffer destination) {
+
+ int copyBits = 0;
+ for (FrameBufferTextureAttachmentSpec attachment : destination.bufferBuilder.textureAttachmentSpecs) {
+ if (attachment.isDepth && (bufferBuilder.hasDepthRenderBuffer || bufferBuilder.hasPackedStencilDepthRenderBuffer)) {
+ copyBits |= GL20.GL_DEPTH_BUFFER_BIT;
+ } else if (attachment.isStencil
+ && (bufferBuilder.hasStencilRenderBuffer || bufferBuilder.hasPackedStencilDepthRenderBuffer)) {
+ copyBits |= GL20.GL_STENCIL_BUFFER_BIT;
+ } else if (colorBufferHandles.size > 0) {
+ copyBits |= GL20.GL_COLOR_BUFFER_BIT;
+ }
+ }
+
+ transfer(destination, copyBits);
+ }
+
+ /** Transfer pixels from this frame buffer to the destination frame buffer. Usually used when using multisample, it resolves
+ * samples from this multisample FBO to a non-multisample as destination in order to be used as textures.
+ * @param destination the destination of the copy (should be same size as this frame buffer).
+ * @param copyBits combination of GL20.GL_COLOR_BUFFER_BIT, GL20.GL_STENCIL_BUFFER_BIT, and GL20.GL_DEPTH_BUFFER_BIT. When
+ * GL20.GL_COLOR_BUFFER_BIT is present, every color buffers will be copied to each corresponding color texture
+ * buffers in the destination framebuffer. */
+ public void transfer (GLFrameBuffer destination, int copyBits) {
+
+ if (destination.getWidth() != getWidth() || destination.getHeight() != getHeight()) {
+ throw new IllegalArgumentException("source and destination frame buffers must have same size.");
+ }
+
+ Gdx.gl.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, framebufferHandle);
+ Gdx.gl.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, destination.framebufferHandle);
+
+ int colorBufferIndex = 0;
+ int attachmentIndex = 0;
+ for (FrameBufferTextureAttachmentSpec attachment : destination.bufferBuilder.textureAttachmentSpecs) {
+ if (attachment.isColorTexture()) {
+ Gdx.gl30.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0 + colorBufferIndex);
+
+ singleInt.clear();
+ singleInt.put(GL30.GL_COLOR_ATTACHMENT0 + attachmentIndex);
+ singleInt.flip();
+ Gdx.gl30.glDrawBuffers(1, singleInt);
+
+ Gdx.gl30.glBlitFramebuffer(0, 0, getWidth(), getHeight(), 0, 0, destination.getWidth(), destination.getHeight(),
+ copyBits, GL20.GL_NEAREST);
+
+ copyBits = GL20.GL_COLOR_BUFFER_BIT;
+ colorBufferIndex++;
+ }
+ attachmentIndex++;
+ }
+ // case of depth and/or stencil only
+ if (copyBits != GL20.GL_COLOR_BUFFER_BIT) {
+ Gdx.gl30.glBlitFramebuffer(0, 0, getWidth(), getHeight(), 0, 0, destination.getWidth(), destination.getHeight(),
+ copyBits, GL20.GL_NEAREST);
+ }
+
+ // restore draw buffers for destination (in case of MRT only)
+ if (destination.defaultDrawBuffers != null) {
+ Gdx.gl30.glDrawBuffers(destination.defaultDrawBuffers.limit(), destination.defaultDrawBuffers);
+ }
+
+ Gdx.gl.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0);
+ Gdx.gl.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
+ }
+
/** @return The OpenGL handle of the framebuffer (see {@link GL20#glGenFramebuffer()}) */
public int getFramebufferHandle () {
return framebufferHandle;
@@ -361,6 +484,12 @@ public int getDepthBufferHandle () {
return depthbufferHandle;
}
+ /** @param n index of the color buffer as added to the frame buffer builder.
+ * @return The OpenGL handle of a color buffer (see {@link GL20#glGenRenderbuffer()}). **/
+ public int getColorBufferHandle (int n) {
+ return colorBufferHandles.get(n);
+ }
+
/** @return The OpenGL handle of the (optional) stencil buffer (see {@link GL20#glGenRenderbuffer()}). May return 0 even if
* stencil buffer enabled */
public int getStencilBufferHandle () {
@@ -445,9 +574,10 @@ public FrameBufferRenderBufferAttachmentSpec (int internalFormat) {
}
public static abstract class GLFrameBufferBuilder> {
- protected int width, height;
+ protected int width, height, samples;
protected Array textureAttachmentSpecs = new Array();
+ protected Array colorRenderBufferSpecs = new Array();
protected FrameBufferRenderBufferAttachmentSpec stencilRenderBufferSpec;
protected FrameBufferRenderBufferAttachmentSpec depthRenderBufferSpec;
@@ -458,8 +588,13 @@ public static abstract class GLFrameBufferBuilder addColorTextureAttachment (int internalFormat, int format, int type) {
@@ -503,6 +638,11 @@ public GLFrameBufferBuilder addDepthRenderBuffer (int internalFormat) {
return this;
}
+ public GLFrameBufferBuilder addColorRenderBuffer (int internalFormat) {
+ colorRenderBufferSpecs.add(new FrameBufferRenderBufferAttachmentSpec(internalFormat));
+ return this;
+ }
+
public GLFrameBufferBuilder addStencilRenderBuffer (int internalFormat) {
stencilRenderBufferSpec = new FrameBufferRenderBufferAttachmentSpec(internalFormat);
hasStencilRenderBuffer = true;
@@ -535,6 +675,10 @@ public FrameBufferBuilder (int width, int height) {
super(width, height);
}
+ public FrameBufferBuilder (int width, int height, int samples) {
+ super(width, height, samples);
+ }
+
@Override
public FrameBuffer build () {
return new FrameBuffer(this);
@@ -546,6 +690,10 @@ public FloatFrameBufferBuilder (int width, int height) {
super(width, height);
}
+ public FloatFrameBufferBuilder (int width, int height, int samples) {
+ super(width, height, samples);
+ }
+
@Override
public FloatFrameBuffer build () {
return new FloatFrameBuffer(this);
@@ -557,6 +705,10 @@ public FrameBufferCubemapBuilder (int width, int height) {
super(width, height);
}
+ public FrameBufferCubemapBuilder (int width, int height, int samples) {
+ super(width, height, samples);
+ }
+
@Override
public FrameBufferCubemap build () {
return new FrameBufferCubemap(this);
diff --git a/tests/gdx-tests-android/assets/data/shaders/shape-renderer-mrt-frag.glsl b/tests/gdx-tests-android/assets/data/shaders/shape-renderer-mrt-frag.glsl
new file mode 100644
index 00000000000..4e4cd244ead
--- /dev/null
+++ b/tests/gdx-tests-android/assets/data/shaders/shape-renderer-mrt-frag.glsl
@@ -0,0 +1,11 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+varying vec4 v_col;
+layout(location = 0) out vec4 colorOut;
+layout(location = 1) out vec4 redOut;
+
+void main() {
+ colorOut = v_col;
+ redOut = vec4(1.0, 0.0, 0.0, 1.0);
+}
diff --git a/tests/gdx-tests-android/assets/data/shaders/shape-renderer-mrt-vert.glsl b/tests/gdx-tests-android/assets/data/shaders/shape-renderer-mrt-vert.glsl
new file mode 100644
index 00000000000..a998e98d7e3
--- /dev/null
+++ b/tests/gdx-tests-android/assets/data/shaders/shape-renderer-mrt-vert.glsl
@@ -0,0 +1,10 @@
+in vec4 a_position;
+in vec4 a_color;
+uniform mat4 u_projModelView;
+out vec4 v_col;
+void main() {
+ gl_Position = u_projModelView * a_position;
+ v_col = a_color;
+ v_col.a *= 255.0 / 254.0;
+ gl_PointSize = 1.0;
+}
diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/gles31/GL31FrameBufferMultisampleMRTTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/gles31/GL31FrameBufferMultisampleMRTTest.java
new file mode 100644
index 00000000000..3a545331782
--- /dev/null
+++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/gles31/GL31FrameBufferMultisampleMRTTest.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright 2022 See AUTHORS file.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+
+package com.badlogic.gdx.tests.gles31;
+
+import com.badlogic.gdx.Application;
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.GL20;
+import com.badlogic.gdx.graphics.GL30;
+import com.badlogic.gdx.graphics.Texture.TextureFilter;
+import com.badlogic.gdx.graphics.g2d.SpriteBatch;
+import com.badlogic.gdx.graphics.glutils.FrameBuffer;
+import com.badlogic.gdx.graphics.glutils.GLFrameBuffer.FrameBufferBuilder;
+import com.badlogic.gdx.graphics.glutils.ShaderProgram;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
+import com.badlogic.gdx.tests.utils.GdxTest;
+import com.badlogic.gdx.tests.utils.GdxTestConfig;
+import com.badlogic.gdx.utils.ScreenUtils;
+
+@GdxTestConfig(requireGL31 = true)
+public class GL31FrameBufferMultisampleMRTTest extends GdxTest {
+
+ private FrameBuffer fbo;
+ private FrameBuffer fboMS;
+ private SpriteBatch batch;
+ private ShapeRenderer shapes;
+ private ShaderProgram shader;
+
+ @Override
+ public void create () {
+
+ int nbSamples = 4;
+
+ fboMS = new FrameBufferBuilder(64, 64, nbSamples).addColorRenderBuffer(GL30.GL_RGBA8).addColorRenderBuffer(GL30.GL_RGBA8)
+ .addDepthRenderBuffer(GL30.GL_DEPTH_COMPONENT24).build();
+
+ fbo = new FrameBufferBuilder(64, 64).addColorTextureAttachment(GL30.GL_RGBA8, GL20.GL_RGBA, GL30.GL_UNSIGNED_BYTE)
+ .addColorTextureAttachment(GL30.GL_RGBA8, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE)
+ .addDepthTextureAttachment(GL30.GL_DEPTH_COMPONENT24, GL30.GL_UNSIGNED_INT).build();
+
+ fbo.getTextureAttachments().get(0).setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
+ fbo.getTextureAttachments().get(1).setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
+ fbo.getTextureAttachments().get(2).setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
+
+ batch = new SpriteBatch();
+
+ ShaderProgram.prependVertexCode = Gdx.app.getType().equals(Application.ApplicationType.Desktop)
+ ? "#version 140\n #extension GL_ARB_explicit_attrib_location : enable\n"
+ : "#version 300 es\n";
+ ShaderProgram.prependFragmentCode = Gdx.app.getType().equals(Application.ApplicationType.Desktop)
+ ? "#version 140\n #extension GL_ARB_explicit_attrib_location : enable\n"
+ : "#version 300 es\n";
+
+ shader = new ShaderProgram(Gdx.files.internal("data/shaders/shape-renderer-mrt-vert.glsl").readString(),
+ Gdx.files.internal("data/shaders/shape-renderer-mrt-frag.glsl").readString());
+
+ shapes = new ShapeRenderer(3, shader);
+
+ }
+
+ @Override
+ public void dispose () {
+ fboMS.dispose();
+ fbo.dispose();
+ shader.dispose();
+ batch.dispose();
+ shapes.dispose();
+ }
+
+ @Override
+ public void render () {
+
+ Gdx.gl.glEnable(GL20.GL_DEPTH_TEST);
+ ScreenUtils.clear(Color.CLEAR, true);
+ batch.getProjectionMatrix().setToOrtho2D(0, 0, 2, 3);
+
+ // render a shape into the non-multisample FBO and display it on the left
+ fbo.begin();
+ ScreenUtils.clear(Color.CLEAR, true);
+ shapes.getProjectionMatrix().setToOrtho2D(0, 0, 1, 1);
+ shapes.begin(ShapeType.Filled);
+ shapes.triangle(0.2f, 0.3f, .9f, .9f, .8f, 0.5f);
+ shapes.end();
+ fbo.end();
+
+ batch.begin();
+ batch.draw(fbo.getTextureAttachments().get(0), 0, 0, 1, 1, 0, 0, 1, 1);
+ batch.draw(fbo.getTextureAttachments().get(1), 0, 1, 1, 1, 0, 0, 1, 1);
+ batch.draw(fbo.getTextureAttachments().get(2), 0, 2, 1, 1, 0, 0, 1, 1);
+ batch.end();
+
+ // render a shape into the multisample FBO, transfer to the other one and display it on the right
+ fboMS.begin();
+ ScreenUtils.clear(Color.CLEAR, true);
+ shapes.getProjectionMatrix().setToOrtho2D(0, 0, 1, 1);
+ shapes.begin(ShapeType.Filled);
+ shapes.triangle(0.2f, 0.3f, .9f, .9f, .8f, 0.5f);
+ shapes.end();
+ fboMS.end();
+
+ fboMS.transfer(fbo);
+
+ batch.begin();
+ batch.draw(fbo.getTextureAttachments().get(0), 1, 0, 1, 1, 0, 0, 1, 1);
+ batch.draw(fbo.getTextureAttachments().get(1), 1, 1, 1, 1, 0, 0, 1, 1);
+ batch.draw(fbo.getTextureAttachments().get(2), 1, 2, 1, 1, 0, 0, 1, 1);
+ batch.end();
+
+ Gdx.gl.glDisable(GL20.GL_DEPTH_TEST);
+ }
+}
diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/gles31/GL31FrameBufferMultisampleTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/gles31/GL31FrameBufferMultisampleTest.java
index 919f73b95b0..5b4f52ad516 100644
--- a/tests/gdx-tests/src/com/badlogic/gdx/tests/gles31/GL31FrameBufferMultisampleTest.java
+++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/gles31/GL31FrameBufferMultisampleTest.java
@@ -16,110 +16,30 @@
package com.badlogic.gdx.tests.gles31;
-import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
-import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
+import com.badlogic.gdx.graphics.glutils.GLFrameBuffer.FrameBufferBuilder;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.tests.utils.GdxTest;
import com.badlogic.gdx.tests.utils.GdxTestConfig;
-import com.badlogic.gdx.utils.Disposable;
-import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.ScreenUtils;
@GdxTestConfig(requireGL31 = true)
public class GL31FrameBufferMultisampleTest extends GdxTest {
- private static class FrameBufferMS implements Disposable {
- public int framebufferHandle;
- public int width, height;
- private int colorBufferHandle;
-
- public FrameBufferMS (Format format, int width, int height, int samples) {
- this.width = width;
- this.height = height;
-
- // create render buffer
- colorBufferHandle = Gdx.gl.glGenRenderbuffer();
- Gdx.gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, colorBufferHandle);
- Gdx.gl31.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER, samples, GL30.GL_RGBA8, width, height);
-
- // create frame buffer
- framebufferHandle = Gdx.gl.glGenFramebuffer();
- Gdx.gl.glBindFramebuffer(GL20.GL_FRAMEBUFFER, framebufferHandle);
-
- // attach render buffer
- Gdx.gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_COLOR_ATTACHMENT0, GL20.GL_RENDERBUFFER,
- colorBufferHandle);
-
- int result = Gdx.gl.glCheckFramebufferStatus(GL20.GL_FRAMEBUFFER);
-
- Gdx.gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, 0);
- Gdx.gl.glBindTexture(GL20.GL_TEXTURE_2D, 0);
- Gdx.gl.glBindFramebuffer(GL20.GL_FRAMEBUFFER, 0);
-
- if (result != GL20.GL_FRAMEBUFFER_COMPLETE) {
- throw new GdxRuntimeException("error");
- }
- }
-
- @Override
- public void dispose () {
- Gdx.gl.glDeleteFramebuffer(framebufferHandle);
- Gdx.gl.glDeleteRenderbuffer(colorBufferHandle);
- }
-
- public void begin () {
- bind();
- setFrameBufferViewport();
- }
-
- protected void setFrameBufferViewport () {
- Gdx.gl20.glViewport(0, 0, width, height);
- }
-
- public void end () {
- end(0, 0, Gdx.graphics.getBackBufferWidth(), Gdx.graphics.getBackBufferHeight());
- }
-
- public void end (int x, int y, int width, int height) {
- unbind();
- Gdx.gl20.glViewport(x, y, width, height);
- }
-
- public void bind () {
- Gdx.gl20.glBindFramebuffer(GL20.GL_FRAMEBUFFER, framebufferHandle);
- }
-
- public static void unbind () {
- Gdx.gl20.glBindFramebuffer(GL20.GL_FRAMEBUFFER, 0);
- }
-
- public int getHeight () {
- return height;
- }
-
- public int getWidth () {
- return width;
- }
-
- public int getFramebufferHandle () {
- return framebufferHandle;
- }
- }
private FrameBuffer fbo;
- private FrameBufferMS fboMS;
+ private FrameBuffer fboMS;
private SpriteBatch batch;
private ShapeRenderer shapes;
@Override
public void create () {
- fboMS = new FrameBufferMS(Format.RGBA8888, 64, 64, 4);
+ fboMS = new FrameBufferBuilder(64, 64, 4).addColorRenderBuffer(GL30.GL_RGBA8).build();
fbo = new FrameBuffer(Format.RGBA8888, 64, 64, false);
fbo.getColorBufferTexture().setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
batch = new SpriteBatch();
@@ -137,6 +57,8 @@ public void dispose () {
@Override
public void render () {
+ ScreenUtils.clear(Color.CLEAR);
+
batch.getProjectionMatrix().setToOrtho2D(0, 0, 2, 2);
// render a line into the non multisample FBO and display it
@@ -161,12 +83,7 @@ public void render () {
shapes.end();
fboMS.end();
- Gdx.gl.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, fboMS.getFramebufferHandle());
- Gdx.gl.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, fbo.getFramebufferHandle());
- Gdx.gl30.glBlitFramebuffer(0, 0, fboMS.getWidth(), fboMS.getHeight(), 0, 0, fbo.getWidth(), fbo.getHeight(),
- GL20.GL_COLOR_BUFFER_BIT, GL20.GL_NEAREST);
- Gdx.gl.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0);
- Gdx.gl.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
+ fboMS.transfer(fbo);
batch.begin();
batch.draw(fbo.getColorBufferTexture(), 1, 0, 1, 1, 0, 0, 1, 1);
diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java
index 19d7fca4cbf..5b6ee13485d 100644
--- a/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java
+++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java
@@ -86,6 +86,7 @@
import com.badlogic.gdx.tests.gles3.InstancedRenderingTest;
import com.badlogic.gdx.tests.gles3.ModelInstancedRenderingTest;
import com.badlogic.gdx.tests.gles3.PixelBufferObjectTest;
+import com.badlogic.gdx.tests.gles31.GL31FrameBufferMultisampleMRTTest;
import com.badlogic.gdx.tests.gles31.GL31FrameBufferMultisampleTest;
import com.badlogic.gdx.tests.gles31.GL31IndirectDrawingIndexedTest;
import com.badlogic.gdx.tests.gles31.GL31IndirectDrawingNonIndexedTest;
@@ -174,6 +175,7 @@ public class GdxTests {
GLES30Test.class,
GL31IndirectDrawingIndexedTest.class,
GL31IndirectDrawingNonIndexedTest.class,
+ GL31FrameBufferMultisampleMRTTest.class,
GL31FrameBufferMultisampleTest.class,
GL31ProgramIntrospectionTest.class,
GL32AdvancedBlendingTest.class,