Jme3-vr: Duplicate calls to update()

Hello VR monkeys!

After pulling the latest changes for jme3-vr, we noticed that the speed of particles in our scene had increased.
It seems the scene is updated twice. Once by simpleUpdate() and another time by VRAppState.update().

The following test code should reproduce the issue. It uses an image from jme3-test-data.

import com.jme3.app.LostFocusBehavior;
import com.jme3.app.SimpleApplication;
import com.jme3.app.VRAppState;
import com.jme3.app.VRConstants;
import com.jme3.app.VREnvironment;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;

public class VRParticles extends SimpleApplication {
    private class DebugEmitter extends ParticleEmitter {
        private long lastUpdate = 0;

        public DebugEmitter(String name, Type type, int numParticles) {
            super(name, type, numParticles);
        }

        @Override
        public void updateFromControl(float tpf) {
            long t = System.nanoTime();
            long dt = t - lastUpdate;
            lastUpdate = t;

            if(dt < 100000) // 100 microseconds
                System.out.println("DebugEmitter dt = " + dt);

            super.updateFromControl(tpf);
        }
    }


    private class TestControl extends AbstractControl {
        private long lastUpdate = 0;
        public int updateCount = 0;

        @Override
        protected void controlUpdate(float tpf) {
            long t = System.nanoTime();
            long dt = t - lastUpdate;
            lastUpdate = t;
            updateCount++;

            if(dt < 100000) // 100 microseconds
                System.out.println("TestControl: dt = " + dt);
        }

        protected void controlRender(RenderManager rm, ViewPort vp) {}
    }


    private final TestControl ctrl = new TestControl();


    private VRParticles(VRAppState vrAppState) {
        stateManager.attach(vrAppState);
    }

    @Override
    public void simpleInitApp() {
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
        Geometry geom = new Geometry("Box", new Box(.5f, .5f, .5f));
        geom.setMaterial(mat);
        mat.getAdditionalRenderState().setWireframe(true);
        mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
        rootNode.attachChild(geom);

        DebugEmitter em = new DebugEmitter("my particle effect", Type.Triangle, 60);
        Material emMat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
        emMat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/shockwave.png"));
        em.setMaterial(emMat);
        em.setParticlesPerSec(2);
        em.setLowLife(4);
        em.setHighLife(4);
        rootNode.attachChild(em);

        rootNode.addControl(ctrl);

        cam.setLocation(new Vector3f(0, 0, 0));
    }


    private int updateCount = 0;

    @Override
    public void simpleUpdate(float tpf) {
        System.out.println("simple update count: " + updateCount + ", control update count: " + ctrl.updateCount);
        updateCount++;
    }


    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.put(VRConstants.SETTING_VRAPI, VRConstants.SETTING_VRAPI_OPENVR_VALUE); // The VR api to use (need to be present on the system)
        settings.put(VRConstants.SETTING_DISABLE_VR, false);          // Enable VR
        settings.put(VRConstants.SETTING_USE_COMPOSITOR, true); // disable the SteamVR compositor (kinda needed at the moment)
        settings.put(VRConstants.SETTING_ENABLE_MIRROR_WINDOW, true); // runs faster when set to false, but will allow mirroring
        settings.put(VRConstants.SETTING_VR_FORCE, false); // render two eyes, regardless of API detection
        settings.put(VRConstants.SETTING_GUI_CURVED_SURFACE, true);
        settings.put(VRConstants.SETTING_GUI_OVERDRAW, true); // show gui even if it is behind things
        settings.put(VRConstants.SETTING_INSTANCE_RENDERING, false); // faster VR rendering, requires some vertex shader changes (see jmevr/shaders/Unshaded.j3md)
        settings.put(VRConstants.SETTING_NO_GUI, false);
        settings.put(VRConstants.SETTING_FLIP_EYES, false);           // Is the HMD eyes have to be inverted.
        settings.put(VRConstants.SETTING_DEFAULT_FOV, 108f);          // The default ield Of View (FOV)
        settings.put(VRConstants.SETTING_DEFAULT_ASPECT_RATIO, 1f);   // The default aspect ratio.

        settings.setSamples(1);
        settings.setSwapBuffers(true);

        VREnvironment env = new VREnvironment(settings);
        env.initialize();
        if(env.isInitialized()) {
            VRAppState vrAppState = new VRAppState(settings, env);
            VRParticles app = new VRParticles(vrAppState);

            settings.setWidth(800);
            settings.setHeight(600);

            app.setLostFocusBehavior(LostFocusBehavior.Disabled);
            app.setSettings(settings);
            app.setShowSettings(false);
            app.start();
        }
    }
}

Output:

simple update count: 1, control update count: 3
TestControl: dt = 43670
DebugEmitter dt = 77401
simple update count: 2, control update count: 5
simple update count: 3, control update count: 7
TestControl: dt = 58427
DebugEmitter dt = 73184
simple update count: 4, control update count: 9
TestControl: dt = 65052
DebugEmitter dt = 77702
simple update count: 5, control update count: 11
TestControl: dt = 75895
DebugEmitter dt = 96375
simple update count: 6, control update count: 13
TestControl: dt = 56017
DebugEmitter dt = 68666
simple update count: 7, control update count: 15
TestControl: dt = 73787
DebugEmitter dt = 93663
simple update count: 8, control update count: 17
TestControl: dt = 54210
DebugEmitter dt = 67160
simple update count: 9, control update count: 19
TestControl: dt = 72582
DebugEmitter dt = 93062
simple update count: 10, control update count: 21

Particle spawn rate and movement speed is also visibly affected.

I noticed this change in VRAppState: OculusVR: Add basic camera positioning · jMonkeyEngine/jmonkeyengine@601ba1c · GitHub

Reverting that one line back to
Iterator<Spatial> spatialIter = application.getViewPort().getScenes().iterator();
fixes the issue because it doesn’t iterate over anything. No spatials are processed in this loop so the duplicate call to update() falls away.

There’s a comment saying //FIXME: check if this code is necessary.
So can we maybe just remove this loop? The loop for the GUI below makes me suspicious though. What’s “manual mode”? Does it require manual updateLogicalState() calls?

What was the reason for your change @ZNix?

2 Likes

It was causing some issues involved with the GUI. I’ll put it back to how it was, and make it tick everything that’s not ticked in SimpleApplication, as I think turning compositing off (for which I’m a little confused why there is support) does leave the main scene in application.getViewPort(), IIRC.

While with SteamVR the application viewport is always empty, apparently that’s not the case on the Rift - the main scene was not getting removed. As such, removing the loop is the easiest way to resolve the issue on both headsets.

(Note that I don’t have a working OSVR installation to test this with).

I’ve opened a PR for this: #755.

2 Likes

Thanks for investigating and the quick fix!

1 Like