Blitted To frame buffer only copies part of source

Context

I’m trying to get multisampling anti aliasing to work in VR. My understanding is (and correct me if I’m wrong) that I need to render to a framebuffer that has multiple samples but then blit to one that hasn’t (because the swapchain images I obtain from OpenXR inherently do not support multiple samples).

JME provides this blitting functionality via renderer#copyFrameBuffer. I am doing all of this within a postRender of an AppState because this is all really pushing things towards the VR headset after JME has finished rendering

Problem

I’m finding that the framebuffer that is blitted to is mostly blank, with just a corner filled in correctly. After a while of experimenting I found that the corner that the size of the corner that is filled in is controlled by the settings for the main window (Not actually the same, they just seem related; if I change the main window size it changes how much of the cameras view copied). Both frame buffers have the same width and height.

image

Minimal reproducible example

In this I have a second camera in the scene, in takes a photo and in the post render stage adds a cube to the scene to show what it saw (I recognise that this is a bit odd, but its the closest I could get to what’s happening in the VR flow). The picture rendered on the cube is what would get sent to the VR headset

public class AntialiasTest extends SimpleApplication {

    int width = 1500;
    int height = 1500;

    FrameBuffer renderedIntoframeBuffer;

    FrameBuffer blittedToFrameBuffer;

    boolean addedBox = false;

    public static void main(String[] args) {
        AntialiasTest app = new AntialiasTest();

        AppSettings settings = new AppSettings(true);
        settings.setSamples(1);
        app.setSettings(settings);
        app.setShowSettings(false);
        app.start();
    }

    @Override
    public void simpleInitApp() {
        getViewPort().setBackgroundColor(ColorRGBA.Brown);

        Camera additionalCamera = new Camera(width, height);
        additionalCamera.setLocation(cam.getLocation());
        additionalCamera.setParallelProjection(false);
        additionalCamera.setFrustumPerspective(90f, (float) width / height, 0.1f, 100f);
        additionalCamera.lookAt(new Vector3f(3,4,0), Vector3f.UNIT_Y);


        ViewPort newViewport = getRenderManager().createMainView(  "Additional Eye", additionalCamera);
        newViewport.setClearFlags(true, true, true);
        newViewport.attachScene(rootNode);
        newViewport.setBackgroundColor(ColorRGBA.Brown);

        renderedIntoframeBuffer = frameBufferRenderedInto();
        blittedToFrameBuffer =  frameBufferBlittedTo();

        newViewport.setOutputFrameBuffer(renderedIntoframeBuffer);

        Box b = new Box(1, 1, 1);
        Geometry geom = new Geometry("Box", b);

        Quaternion q = new Quaternion();
        q.fromAngleAxis(FastMath.PI/3, new Vector3f(1,1,1));
        geom.setLocalRotation(q);

        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        geom.setMaterial(mat);

        rootNode.attachChild(geom);

        getStateManager().attach(new BaseAppState(){
            @Override protected void initialize(Application app){}
            @Override protected void cleanup(Application app){}
            @Override protected void onEnable(){}
            @Override protected void onDisable(){}

            @Override
            public void postRender(){
                renderer.copyFrameBuffer(renderedIntoframeBuffer, blittedToFrameBuffer, true, false);
                if(!addedBox){
                    updateTextureFromFrameBuffer(blittedToFrameBuffer); //<-- if I change this to renderedIntoframeBuffer it looks fine
                }
            }
        });

    }

    private FrameBuffer frameBufferRenderedInto(){
        int samples = 1;
        FrameBuffer frameBuffer = new FrameBuffer(width, height,samples);
        frameBuffer.setName("OpenXR eye buffer copied mode");
        Texture2D texture = new Texture2D(width, height, samples, Image.Format.RGBA8);
        Texture2D msDepth = new Texture2D(width, height, samples, Image.Format.Depth);
        frameBuffer.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(texture));
        frameBuffer.setDepthTarget(FrameBuffer.FrameBufferTarget.newTarget(msDepth));
        return frameBuffer;
    }

    private FrameBuffer frameBufferBlittedTo(){
        FrameBuffer frameBuffer = new FrameBuffer(width, height, 1);
        Texture2D texture = new Texture2D(width, height, 1, Image.Format.RGBA8);
        frameBuffer.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(texture));
        return frameBuffer;
    }
    /**
    * Render the FrameBuffer  to a cube to see what it sees
    */
    private void updateTextureFromFrameBuffer(FrameBuffer frameBuffer) {
        Texture2D tex = (Texture2D) frameBuffer.getColorTarget().getTexture();

        Material texturedMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        texturedMat.setTexture("ColorMap", tex);

        Box boxWithTexture = new Box(1, 1, 1);
        Geometry texturedGeom = new Geometry("TexturedBox", boxWithTexture);
        texturedGeom.setMaterial(texturedMat);

        texturedGeom.setLocalTranslation(2, 0, -5);

        rootNode.attachChild(texturedGeom);
    }
}


If instead I render the renderedIntoframeBuffer on the cube it looks correct, so whatever is going wrong it’s in the copy

updateTextureFromFrameBuffer(renderedIntoframeBuffer);

image

Any idea what is going on? And how I can copy the whole of the secondary scene from one frame buffer to another?

This occurs both in JME 3.6.1 and 3.7.0-alpha3

If the result changes with the window resolution it hints to gl_viewport. i am not sure if the blitting operation is affected by the gl_viewport settings or not. but that would be the first thing i would check.

jme’s blitframebuffer uses hardcoded gl.nearest as filter mode, not sure if it interferes with your multisampeling idea, so just an fyi.

2 Likes

If the result changes with the window resolution it hints to gl_viewport. i am not sure if the blitting operation is affected by the gl_viewport settings or not. but that would be the first thing i would check.

Adding a renderer.setViewPort(0,0, width, height); before the copyFrameBuffer didn’t help but it got me thinking!

Adding renderer.clearClipRect(); before the call to copyFrameBuffer sorts everything! Thanks for the pointer in the right direction @zzuegg!

I wonder if I should be putting the clip rectangle back the way I found it or if something else has incorrectly left it on (if I should be putting it back the way I found it there doesn’t seem to be a getter for its current state).

Edit: what is setting it is: renderViewPort->setCamera->setViewPort->setClipRect. I wonder if renderViewPort ought to be clearing the clip rectangle at the end?

jme’s blitframebuffer uses hardcoded gl.nearest as filter mode, not sure if it interferes with your multisampeling idea, so just an fyi.

That’s ok, I believe that’s what I want anyway

2 Likes

I raised an issue and pull request that should sort this issue. In the meantime I’ve added a workaround into Tamarin to allow it to support MSAA

1 Like