[Solved] RTT && multisampling

Hey guys,



I am rendering a scene to a Texture (RGBA) and have Multisampling activated in the driver (overriding application settings). However, the scene rendered to the Texture is NOT anti aliased. How can I change that?



Thanks

Hi, as far as I know there is no RTT FBO multisampling support in JME (this appears from the JME LWJGL RTT implementation).



Multisampled FBOs are a quite recent GL implementation and it requires some special extensions to be present in the driver. But, while you have almost no hopes to see it in the Intel boards, It’s already implemented in common ATI and NVidia boards (at least in their Windows drivers).



Check the http://www.opengl.org/registry/specs/EXT/framebuffer_multisample.txt

http://www.opengl.org/registry/specs/EXT/framebuffer_blit.txt



Maybe you can submit a feature request to the JME developers.



I wrote my own FBO RTT for JME and I have to say that the results are incredibly good. But the API is a little bit different from the RTT api of JME. This comes from the TestRenderToTexture sample, modified to use my RTT.



// setup the RTT renderer

Renderer myTextureRenderer = display.createRenderer(512, 512);

SGLJMERendererImage rendererImage = new SGLJMERendererImage(myTextureRenderer, fakeScene, 16);



// get the texture and set in TextureState

Texture2D fakeTex = rendererImage.getJMETexture2D();

ts.setTexture(fakeTex, 0);







// update scene texture (this is the animation loop)

rendererImage.updateImmediately();



Cheers,



Mik



A sample pic is here.

Note that the jagged egdes on black are the result of blending towards 0x00000000 (and this is what I want, as I use the resulting image as a “sprite” for another application).










cool, is it possible to get the source code of your custom texture renderer and surroundings?  :wink:

Eh, is not that simple as, you can guess it, the SGLJMERenderedImage is kind-of bridge between JME and my SGL graphics libraries.



I got my "inspiration" from this article:



http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=233432



Maybe I could send my java initialization code, but I'm leaving for Amsterdam now (www.IBC.org). I'll be back to you if you need it.

jME3 supports multisampling in framebuffers. You can see the code for that here: http://code.google.com/p/jmonkeyengine/source/browse/branches/mf_jme3test/src/com/g3d/renderer/lwjgl/LwjglRenderer.java#1005

It's very simple, you just call glRenderbufferStorageMultisampleEXT instead of glRenderbufferStorageEXT. You also need to copy the multisampled FBO to a normal FBO if you want to use it in a texture. For that you have to use glBlitFramebufferEXT.

Momoko_Fan said:

You also need to copy the multisampled FBO to a normal FBO if you want to use it in a texture. For that you have to use glBlitFramebufferEXT.


How do I do that? What i have is this:


Index: src/com/jme/renderer/lwjgl/LWJGLTextureRenderer.java
===================================================================
--- src/com/jme/renderer/lwjgl/LWJGLTextureRenderer.java   (revision 4684)
+++ src/com/jme/renderer/lwjgl/LWJGLTextureRenderer.java   (working copy)
@@ -42,6 +42,7 @@
 
 import org.lwjgl.opengl.ARBDrawBuffers;
 import org.lwjgl.opengl.ARBTextureFloat;
+import org.lwjgl.opengl.EXTFramebufferMultisample;
 import org.lwjgl.opengl.EXTFramebufferObject;
 import org.lwjgl.opengl.GL11;
 import org.lwjgl.opengl.GL14;
@@ -87,6 +88,9 @@
     private static boolean inited = false;
     private static boolean isSupported = true;
     private static boolean supportsMultiDraw = false;
+    private static boolean supportsMultiSample = true;
+    private static int maxSamples = 0;
+    private int samplesUsed = 0;
     private static int maxDrawBuffers = 1;
     private static IntBuffer attachBuffer = null;
     private boolean usingDepthRB = false;
@@ -97,10 +101,24 @@
 
     public LWJGLTextureRenderer(int width, int height,
             LWJGLDisplaySystem display, LWJGLRenderer parentRenderer) {
+        this(width, height, 0, display, parentRenderer);
+    }
+       
+       
+    public LWJGLTextureRenderer(int width, int height, int samples,
+            LWJGLDisplaySystem display, LWJGLRenderer parentRenderer) {
+        this.samplesUsed = samples;
         this.display = display;
         this.parentRenderer = parentRenderer;
        
         if (!inited) {
+            supportsMultiSample = GLContext.getCapabilities().GL_EXT_framebuffer_multisample;
+            if (supportsMultiSample) {
+                IntBuffer buf = BufferUtils.createIntBuffer(16);
+                GL11.glGetInteger(EXTFramebufferMultisample.GL_MAX_SAMPLES_EXT, buf);
+                maxSamples = buf.get(0);
+                logger.log(Level.FINER, "FBO Max Samples: {0}", maxSamples);
+            }
             isSupported = GLContext.getCapabilities().GL_EXT_framebuffer_object;
             supportsMultiDraw = GLContext.getCapabilities().GL_ARB_draw_buffers;
             if (supportsMultiDraw) {
@@ -164,10 +182,17 @@
         depthRBID = buffer.get(0);
         EXTFramebufferObject.glBindRenderbufferEXT(
                 EXTFramebufferObject.GL_RENDERBUFFER_EXT, depthRBID);
-        EXTFramebufferObject.glRenderbufferStorageEXT(
-                EXTFramebufferObject.GL_RENDERBUFFER_EXT,
-                GL11.GL_DEPTH_COMPONENT, width, height);
-
+        samplesUsed = Math.min(maxSamples, samplesUsed);
+        if(samplesUsed > 0) {
+            EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(
+                    EXTFramebufferObject.GL_RENDERBUFFER_EXT, samplesUsed,
+                    GL11.GL_DEPTH_COMPONENT, width, height);
+        } else {
+            EXTFramebufferObject.glRenderbufferStorageEXT(
+                    EXTFramebufferObject.GL_RENDERBUFFER_EXT,
+                    GL11.GL_DEPTH_COMPONENT, width, height);
+        }
+       
         this.width = width;
         this.height = height;
 
@@ -799,6 +824,11 @@
                         "FrameBuffer: "
                                 + fboID
                                 + ", has caused a GL_FRAMEBUFFER_UNSUPPORTED_EXT exception");
+            case EXTFramebufferMultisample.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:
+                throw new RuntimeException(
+                        "FrameBuffer: "
+                                + fboID
+                                + ", has caused a GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT");
             default:
                 throw new RuntimeException(
                         "Unexpected reply from glCheckFramebufferStatusEXT: "
Index: src/com/jme/system/DisplaySystem.java
===================================================================
--- src/com/jme/system/DisplaySystem.java   (revision 4682)
+++ src/com/jme/system/DisplaySystem.java   (working copy)
@@ -773,6 +773,21 @@
      */
     public abstract TextureRenderer createTextureRenderer(int width,
             int height, TextureRenderer.Target target);
+   
+    /**
+     * Create a TextureRenderer using the underlying system.
+     *
+     * @param width
+     *            width of texture
+     * @param height
+     *            height of texture
+     * @param samples
+     *            AA samples used when rendering to texture
+     * @param target
+     * @return A TextureRenderer for the display system.
+     */
+    public abstract TextureRenderer createTextureRenderer(int width,
+            int height, int samples, TextureRenderer.Target target);
 
     /**
      * Translate world to screen coordinates
Index: src/com/jme/system/dummy/DummyDisplaySystem.java
===================================================================
--- src/com/jme/system/dummy/DummyDisplaySystem.java   (revision 4682)
+++ src/com/jme/system/dummy/DummyDisplaySystem.java   (working copy)
@@ -39,6 +39,7 @@
 import com.jme.renderer.RenderContext;
 import com.jme.renderer.Renderer;
 import com.jme.renderer.TextureRenderer;
+import com.jme.renderer.TextureRenderer.Target;
 import com.jme.system.DisplaySystem;
 import com.jme.system.canvas.JMECanvas;
 
@@ -148,6 +149,11 @@
     }
 
     @Override
+    public TextureRenderer createTextureRenderer(int width, int height, int samples, Target target) {
+        return null;
+    }
+
+    @Override
     protected void updateDisplayBGC() {
     }
 
Index: src/com/jme/system/jogl/JOGLDisplaySystem.java
===================================================================
--- src/com/jme/system/jogl/JOGLDisplaySystem.java   (revision 4682)
+++ src/com/jme/system/jogl/JOGLDisplaySystem.java   (working copy)
@@ -355,6 +355,15 @@
         return new JOGLTextureRenderer(width, height, this, renderer);
     }
 
+    @Override
+    public TextureRenderer createTextureRenderer(int width, int height, int samples, Target target) {
+        if (!isCreated()) {
+            return null;
+        }
+
+        return new JOGLTextureRenderer(width, height, this, renderer);
+    }
+
     public static JOGLAWTCanvas createGLCanvas() {
         // Initialize the OpenGL requested capabilities.
         final GLCapabilities caps = new GLCapabilities();
Index: src/com/jme/system/lwjgl/LWJGLDisplaySystem.java
===================================================================
--- src/com/jme/system/lwjgl/LWJGLDisplaySystem.java   (revision 4682)
+++ src/com/jme/system/lwjgl/LWJGLDisplaySystem.java   (working copy)
@@ -53,6 +53,7 @@
 import com.jme.renderer.RenderContext;
 import com.jme.renderer.Renderer;
 import com.jme.renderer.TextureRenderer;
+import com.jme.renderer.TextureRenderer.Target;
 import com.jme.renderer.lwjgl.LWJGLPbufferTextureRenderer;
 import com.jme.renderer.lwjgl.LWJGLRenderer;
 import com.jme.renderer.lwjgl.LWJGLTextureRenderer;
@@ -337,6 +338,25 @@
         return textureRenderer;
     }
 
+    @Override
+    public TextureRenderer createTextureRenderer(int width, int height, int samples, Target target) {
+        if ( !isCreated() ) {
+            return null;
+        }
+
+        TextureRenderer textureRenderer = new LWJGLTextureRenderer( width, height, samples,
+                this, renderer);
+
+        if (!textureRenderer.isSupported()) {
+            logger.info("FBO not supported, attempting Pbuffer.");
+
+            textureRenderer = new LWJGLPbufferTextureRenderer( width, height,
+                    this, renderer, target);
+        }
+       
+        return textureRenderer;
+    }
+
     /**
      * <code>getValidDisplayMode</code> returns a <code>DisplayMode</code>
      * object that has the requested width, height and color depth. If there is



I have to admit, I dont have any clue how FBOs work, someone else will have to do it  :| With the patch above, the check of the FBO will throw an exceptionreturn:


Sep 11, 2009 10:39:21 AM class com.jme.renderer.lwjgl.LWJGLTextureRenderer render(Spatial, Texture, boolean)
SEVERE: Exception
java.lang.RuntimeException: Unexpected reply from glCheckFramebufferStatusEXT: 36182
   at com.jme.renderer.lwjgl.LWJGLTextureRenderer.checkFBOComplete(LWJGLTextureRenderer.java:828)
   at com.jme.renderer.lwjgl.LWJGLTextureRenderer.setupForSingleTexDraw(LWJGLTextureRenderer.java:751)
   at com.jme.renderer.lwjgl.LWJGLTextureRenderer.render(LWJGLTextureRenderer.java:594)
   at com.jme.renderer.lwjgl.LWJGLTextureRenderer.render(LWJGLTextureRenderer.java:570)



which is a GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT

help, anyone?

Is the color component multisampled too? I don't see that in the patch. Look at how glBlitFramebuffer is used in jME3, you need to bind GL_READ_FRAMEBUFFER AND GL_WRITE_FRAMEBUFFER to the appropriate values and then call glBlitFramebuffer with 0,0 for x,y and the width and height to whatever the width and height is of the framebuffers you're trying to blit. Here's the relevant method in jME3: http://code.google.com/p/jmonkeyengine/source/browse/branches/mf_jme3test/src/com/g3d/renderer/lwjgl/LwjglRenderer.java#912

Momoko_Fan said:

Is the color component multisampled too? I don't see that in the patch. Look at how glBlitFramebuffer is used in jME3, you need to bind GL_READ_FRAMEBUFFER AND GL_WRITE_FRAMEBUFFER to the appropriate values and then call glBlitFramebuffer with 0,0 for x,y and the width and height to whatever the width and height is of the framebuffers you're trying to blit. Here's the relevant method in jME3: http://code.google.com/p/jmonkeyengine/source/browse/branches/mf_jme3test/src/com/g3d/renderer/lwjgl/LwjglRenderer.java#912


Cool, that helped resolve the error above.

What I have now is that:
http://pastebin.com/m264d1139

And the rendering works without exceptions, however Display.swapBuffers() crashes:


Oct 16, 2009 12:06:27 PM class jmetest.renderer.TestRenderToTexture start()
SEVERE: Exception in game loop
org.lwjgl.opengl.OpenGLException: Invalid enum (1280)
   at org.lwjgl.opengl.Util.checkGLError(Util.java:54)
   at org.lwjgl.opengl.Display.swapBuffers(Display.java:640)
   at org.lwjgl.opengl.Display.update(Display.java:660)
   at com.jme.renderer.lwjgl.LWJGLRenderer.displayBackBuffer(LWJGLRenderer.java:532)
   at com.jme.app.BaseGame.start(BaseGame.java:90)
   at jmetest.renderer.TestRenderToTexture.main(TestRenderToTexture.java:81)



I think its not much thats missing Momoko_Fan. It would be really cool if you could take a look at it and test/fix it for me  ;)

bump Momoko_Fan :wink:

Hard to say why it crashes… Try to use lwjgl debug build, lwjgl-debug.jar, instead of the usual lwjgl.jar. It should then crash right after the erroneous call.

Momoko_Fan said:

Hard to say why it crashes.. Try to use lwjgl debug build, lwjgl-debug.jar, instead of the usual lwjgl.jar. It should then crash right after the erroneous call.


that didn't help, but what did help was a checkFBOComplete() call in the end of the constructor (it was missing there) and that revealed some missing setup stuff.

However, i got it to work and will contribute it to jme 2.0  :)
http://www.jmonkeyengine.com/forum/index.php?topic=12377.0

EDIT: one more problem: the edges (where the multisampling kicks in) have the color of the background (of course). However, if the color of the background (of the TextureRenderer) has an alpha of 0 (e.g. new ColorRGBA(1, 0, 0, 0)) the color at the edges (the multisampled pixels) appear in that color (in the example, red). the rest of the background however is transparent as expected (in the example the rendered texture is rendered onto a greyish background.



Suggestions?

Maybe the background color should be 0,0,0,0? Why would you have an invisible red there?

Momoko_Fan said:

Maybe the background color should be 0,0,0,0? Why would you have an invisible red there?


Just to show the bug  ;) when its 0, 0, 0, 0, the multisampled pixels are black and with 1, 1, 1, 0 they are white, although the rest of the background is transparent. You know what I mean?

EDIT: Playing around with the BlendEquation of the BlendState fixed it.

Normally to avoid that kind of artifact you should work with alpha-premultiplied pixels. I know this is not so straightforward with opengl.



Can you tell us the BlendEquation of the BlendState you're using ?

MikOfClassX said:

Can you tell us the BlendEquation of the BlendState you're using ?


I realize its not so much a solution but a work-around, i used (0, 0, 0, 0) for background color and then I used BlendEquation.Add and the artifacts seem to disappear  :|

If you have a better workaround or even THE solution for this I'd be happy to get rid of the darn bug  ;)

We are talking about Porter-duff blend equations SRC_OVER.



IMHO the only way to solve the issue is to use premultiplied ARGB values in the texture, then use a blend equation of GL_ONE, GL_ONE_MINUS_SRC_ALPHA. Last step is to unpremultiply the dest pixels.


GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);


I'm using RRT and pixel shaders.

If you're not going to use the "hard way" of premultiplied pixels, you could try to use this equation:

EXTBlendFuncSeparate.glBlendFuncSeparateEXT(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);



Which gives some nice results in my test cases.

Cheers,

Mik