Ver código fonte

Nifty-1.3.3-SNAPSHOT (build 2013-03-09) added + first Nifty batched renderer integration (optional)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10478 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
voi..om 12 anos atrás
pai
commit
75569f4c04

+ 1 - 0
engine/lib/nblibraries.properties

@@ -45,6 +45,7 @@ libs.niftygui1.3.classpath=\
     ${base}/niftygui/nifty-examples.jar;\
     ${base}/niftygui/nifty-style-black.jar;\
     ${base}/niftygui/nifty.jar;\
+    ${base}/niftygui/jglfont-core.jar;\
     ${base}/niftygui/xmlpull-xpp3.jar
 libs.niftygui1.3.javadoc=\
     ${base}/niftygui/nifty-default-controls-javadoc.jar!//;\

+ 5 - 5
engine/lib/niftygui/VERSION.txt

@@ -1,8 +1,8 @@
-Last Updated 7/10/2012
+Last Updated 9/3/2013
 
 Component		Version
 ---------------------------------------------------------------------
-nifty			nifty-1.3.2
-nifty-default-controls	nifty-default-controls-1.3.2
-nifty-style-black	nifty-style-black-1.3.2
-nifty-examples		nifty-examples-1.3.2
+nifty			nifty-1.3.3-SNAPSHOT
+nifty-default-controls	nifty-default-controls-1.3.3-SNAPSHOT
+nifty-style-black	nifty-style-black-1.3.3-SNAPSHOT
+nifty-examples		nifty-examples-1.3.3-SNAPSHOT

BIN
engine/lib/niftygui/jglfont-core.jar


BIN
engine/lib/niftygui/nifty-default-controls-javadoc.jar


BIN
engine/lib/niftygui/nifty-default-controls.jar


BIN
engine/lib/niftygui/nifty-examples-javadoc.jar


BIN
engine/lib/niftygui/nifty-examples.jar


BIN
engine/lib/niftygui/nifty-javadoc.jar


BIN
engine/lib/niftygui/nifty-style-black.jar


BIN
engine/lib/niftygui/nifty.jar


+ 514 - 0
engine/src/niftygui/com/jme3/niftygui/JmeBatchRenderBackend.java

@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.niftygui;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.asset.TextureKey;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.BufferUtils;
+
+import de.lessvoid.nifty.batch.spi.BatchRenderBackend;
+import de.lessvoid.nifty.render.BlendMode;
+import de.lessvoid.nifty.spi.render.MouseCursor;
+import de.lessvoid.nifty.tools.Color;
+import de.lessvoid.nifty.tools.ObjectPool;
+import de.lessvoid.nifty.tools.ObjectPool.Factory;
+import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader;
+
+/**
+ * Nifty GUI BatchRenderBackend Implementation for jMonkeyEngine.
+ * @author void
+ */
+public class JmeBatchRenderBackend implements BatchRenderBackend {
+  private static Logger log = Logger.getLogger(JmeBatchRenderBackend.class.getName());
+
+  private final ObjectPool<Batch> batchPool;
+  private final List<Batch> batches = new ArrayList<Batch>();
+
+  // a modify texture call needs a jme Renderer to execute. if we're called to modify a texture but don't
+  // have a Renderer yet - since it was not initialized on the jme side - we'll cache the modify texture calls
+  // in here and execute them later (at the next beginFrame() call).
+  private final List<ModifyTexture> modifyTextureCalls = new ArrayList<ModifyTexture>();
+
+  private RenderManager renderManager;
+  private NiftyJmeDisplay display;
+  private Texture2D textureAtlas;
+  private Batch currentBatch;
+  private Matrix4f tempMat = new Matrix4f();
+  private ByteBuffer initialData;
+
+  // this is only used for debugging purpose and will make the removed textures filled with a color
+  private boolean fillRemovedTexture =
+      Boolean.getBoolean(System.getProperty(JmeBatchRenderBackend.class.getName() + ".fillRemovedTexture", "false"));
+
+  public JmeBatchRenderBackend(final NiftyJmeDisplay display) {
+    this.display = display;
+    this.batchPool = new ObjectPool<Batch>(2, new Factory<Batch>() {
+      @Override
+      public Batch createNew() {
+        return new Batch();
+      }
+    });
+  }
+
+  public void setRenderManager(final RenderManager rm) {
+    this.renderManager = rm;
+  }
+
+  @Override
+  public void setResourceLoader(final NiftyResourceLoader resourceLoader) {
+  }
+
+  @Override
+  public int getWidth() {
+    return display.getWidth();
+  }
+
+  @Override
+  public int getHeight() {
+    return display.getHeight();
+  }
+
+  @Override
+  public void beginFrame() {
+    log.fine("beginFrame()");
+
+    for (int i=0; i<batches.size(); i++) {
+      batchPool.free(batches.get(i));
+    }
+    batches.clear();
+
+    // in case we have pending modifyTexture calls we'll need to execute them now
+    if (!modifyTextureCalls.isEmpty()) {
+      Renderer renderer = display.getRenderer();
+      for (int i=0; i<modifyTextureCalls.size(); i++) {
+        modifyTextureCalls.get(i).execute(renderer);
+      }
+      modifyTextureCalls.clear();
+    }
+  }
+
+  @Override
+  public void endFrame() {
+    log.fine("endFrame");
+  }
+
+  @Override
+  public void clear() {
+  }
+
+  // TODO: Cursor support
+
+  @Override
+  public MouseCursor createMouseCursor(final String filename, final int hotspotX, final int hotspotY) throws IOException {
+    return new MouseCursor() {
+      public void dispose() {
+      }
+  };
+  }
+
+  @Override
+  public void enableMouseCursor(final MouseCursor mouseCursor) {
+  }
+
+  @Override
+  public void disableMouseCursor() {
+  }
+
+  @Override
+  public void createAtlasTexture(final int width, final int height) {
+    try {
+      createAtlasTextureInternal(width, height);
+
+      // we just initialize a second buffer here that will replace the texture atlas image
+      initialData = BufferUtils.createByteBuffer(width*height*4);
+      for (int i=0; i<width*height; i++) {
+        initialData.put((byte) 0x00);
+        initialData.put((byte) 0xff);
+        initialData.put((byte) 0x00);
+        initialData.put((byte) 0xff);
+      }
+    } catch (Exception e) {
+      log.log(Level.WARNING, e.getMessage(), e);
+    }
+  }
+
+  @Override
+  public void clearAtlasTexture(final int width, final int height) {
+    initialData.rewind();
+    textureAtlas.getImage().setData(initialData);
+  }
+
+  @Override
+  public Image loadImage(final String filename) {
+    TextureKey key = new TextureKey(filename, false);
+    key.setAnisotropy(0);
+    key.setAsCube(false);
+    key.setGenerateMips(false);
+
+    Texture2D texture = (Texture2D) display.getAssetManager().loadTexture(key);
+    return new ImageImpl(texture.getImage());
+  }
+
+  @Override
+  public void addImageToTexture(final Image image, final int x, final int y) {
+    ImageImpl imageImpl = (ImageImpl) image;
+    imageImpl.modifyTexture(this, textureAtlas, x, y);
+  }
+
+  @Override
+  public void beginBatch(final BlendMode blendMode) {
+    batches.add(batchPool.allocate());
+    currentBatch = batches.get(batches.size() - 1);
+    currentBatch.begin(blendMode);
+  }
+
+  @Override
+  public void addQuad(
+      final float x,
+      final float y,
+      final float width,
+      final float height,
+      final Color color1,
+      final Color color2,
+      final Color color3,
+      final Color color4,
+      final float textureX,
+      final float textureY,
+      final float textureWidth,
+      final float textureHeight) {
+    if (!currentBatch.canAddQuad()) {
+      beginBatch(currentBatch.getBlendMode());
+    }
+    currentBatch.addQuadInternal(x, y, width, height, color1, color2, color3, color4, textureX, textureY, textureWidth, textureHeight);
+  }
+
+  @Override
+  public int render() {
+    for (int i=0; i<batches.size(); i++) {
+      Batch batch = batches.get(i);
+      batch.render();
+    }
+    return batches.size();
+  }
+
+  @Override
+  public void removeFromTexture(final Image image, final int x, final int y, final int w, final int h) {
+    // Since we clear the whole texture when we switch screens it's not really necessary to remove data from the
+    // texture atlas when individual textures are removed. If necessary this can be enabled with a system property.
+    if (!fillRemovedTexture) {
+      return;
+    }
+
+    ByteBuffer initialData = BufferUtils.createByteBuffer(image.getWidth()*image.getHeight()*4);
+    for (int i=0; i<image.getWidth()*image.getHeight(); i++) {
+      initialData.put((byte) 0xff);
+      initialData.put((byte) 0x00);
+      initialData.put((byte) 0x00);
+      initialData.put((byte) 0xff);
+    }
+    initialData.rewind();
+    modifyTexture(
+        textureAtlas,
+        new com.jme3.texture.Image(Format.RGBA8, image.getWidth(), image.getHeight(), initialData),
+        x,
+        y);
+  }
+
+  // internal implementations
+
+  private void createAtlasTextureInternal(final int width, final int height) throws Exception {
+    ByteBuffer initialData = BufferUtils.createByteBuffer(width*height*4);
+    for (int i=0; i<width*height*4; i++) {
+      initialData.put((byte) 0x80);
+    }
+    initialData.rewind();
+
+    textureAtlas = new Texture2D(new com.jme3.texture.Image(Format.RGBA8, width, height, initialData));
+    textureAtlas.setMinFilter(MinFilter.NearestNoMipMaps);
+    textureAtlas.setMagFilter(MagFilter.Nearest);
+  }
+
+  private void modifyTexture(
+      final Texture2D textureAtlas,
+      final com.jme3.texture.Image image,
+      final int x,
+      final int y) {
+    Renderer renderer = display.getRenderer();
+    if (renderer == null) {
+      // we have no renderer (yet) so we'll need to cache this call to the next beginFrame() call
+      modifyTextureCalls.add(new ModifyTexture(textureAtlas, image, x, y));
+      return;
+    }
+
+    // all is well, we can execute the modify right away
+    renderer.modifyTexture(textureAtlas, image, x, y);
+  }
+
+  /**
+   * Simple BatchRenderBackend.Image implementation that will transport the dimensions of an image as well as the
+   * actual bytes from the loadImage() to the addImageToTexture() method.
+   *
+   * @author void
+   */
+  private static class ImageImpl implements BatchRenderBackend.Image {
+    private final com.jme3.texture.Image image;
+
+    public ImageImpl(final com.jme3.texture.Image image) {
+      this.image = image;
+    }
+
+    public void modifyTexture(
+        final JmeBatchRenderBackend backend,
+        final Texture2D textureAtlas,
+        final int x,
+        final int y) {
+      backend.modifyTexture(textureAtlas, image, x, y);
+    }
+
+    @Override
+    public int getWidth() {
+      return image.getWidth();
+    }
+
+    @Override
+    public int getHeight() {
+      return image.getHeight();
+    }
+  }
+
+  /**
+   * Used to delay ModifyTexture calls in case we don't have a JME3 Renderer yet.
+   * @author void
+   */
+  private static class ModifyTexture {
+    private Texture2D atlas;
+    private com.jme3.texture.Image image;
+    private int x;
+    private int y;
+
+    private ModifyTexture(final Texture2D atlas, final com.jme3.texture.Image image, final int x, final int y) {
+      this.atlas = atlas;
+      this.image = image;
+      this.x = x;
+      this.y = y;
+    }
+
+    public void execute(final Renderer renderer) {
+      renderer.modifyTexture(atlas, image, x, y);
+    }
+  }
+
+  /**
+   * This class helps us to manage the batch data. We'll keep a bunch of instances of this class around that will be
+   * reused when needed. Each Batch instance provides room for a certain amount of vertices and we'll use a new Batch
+   * when we exceed this amount of data.
+   *
+   * @author void
+   */
+  private class Batch {
+    // 4 vertices per quad and 8 vertex attributes for each vertex:
+    // - 2 x pos
+    // - 2 x texture
+    // - 4 x color
+    //
+    // stored into 3 different buffers: position, texture coords, vertex color
+    // and an additional buffer for indexes
+    //
+    // there is a fixed amount of primitives per batch. if we run out of vertices we'll start a new batch.
+    private final static int BATCH_MAX_QUADS = 2000;
+    private final static int BATCH_MAX_VERTICES = BATCH_MAX_QUADS * 4;
+
+    // individual buffers for all the vertex attributes
+    private final VertexBuffer vertexPos = new VertexBuffer(Type.Position);
+    private final VertexBuffer vertexTexCoord = new VertexBuffer(Type.TexCoord);
+    private final VertexBuffer vertexColor = new VertexBuffer(Type.Color);
+    private final VertexBuffer indexBuffer = new VertexBuffer(Type.Index);
+
+    private final Mesh mesh = new Mesh();
+    private final Geometry meshGeometry = new Geometry("nifty-quad", mesh);
+    private final RenderState renderState = new RenderState();
+
+    private FloatBuffer vertexPosBuffer;
+    private FloatBuffer vertexTexCoordBuffer;
+    private FloatBuffer vertexColorBuffer;
+    private ShortBuffer indexBufferBuffer;
+
+    // number of quads already added to this batch.
+    private int quadCount;
+    private short globalVertexIndex;
+
+    // current blend mode
+    private BlendMode blendMode = BlendMode.BLEND;
+    private Material material;
+
+    public Batch() {
+      // setup mesh
+      vertexPos.setupData(Usage.Stream, 2, VertexBuffer.Format.Float, BufferUtils.createFloatBuffer(BATCH_MAX_VERTICES * 2));
+      vertexPosBuffer = (FloatBuffer) vertexPos.getData();
+      mesh.setBuffer(vertexPos);
+
+      vertexTexCoord.setupData(Usage.Stream, 2, VertexBuffer.Format.Float, BufferUtils.createFloatBuffer(BATCH_MAX_VERTICES * 2));
+      vertexTexCoordBuffer = (FloatBuffer) vertexTexCoord.getData();
+      mesh.setBuffer(vertexTexCoord);
+
+      vertexColor.setupData(Usage.Stream, 4, VertexBuffer.Format.Float, BufferUtils.createFloatBuffer(BATCH_MAX_VERTICES * 4));
+      vertexColorBuffer = (FloatBuffer) vertexColor.getData();
+      mesh.setBuffer(vertexColor);
+
+      indexBuffer.setupData(Usage.Stream, 3, VertexBuffer.Format.UnsignedShort, BufferUtils.createShortBuffer(BATCH_MAX_QUADS * 2 * 3));
+      indexBufferBuffer = (ShortBuffer) indexBuffer.getData();
+      mesh.setBuffer(indexBuffer);
+
+      material = new Material(display.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+      material.setBoolean("VertexColor", true);
+
+      renderState.setDepthTest(false);
+      renderState.setDepthWrite(false);
+    }
+
+    public void begin(final BlendMode blendMode) {
+      this.blendMode = blendMode;
+      quadCount = 0;
+      globalVertexIndex = 0;
+      vertexPosBuffer.clear();
+      vertexTexCoordBuffer.clear();
+      vertexColorBuffer.clear();
+      indexBufferBuffer.clear();
+    }
+
+    public BlendMode getBlendMode() {
+      return blendMode;
+    }
+
+    public void render() {
+      renderState.setBlendMode(convertBlend(blendMode));
+
+      vertexPosBuffer.flip();
+      vertexPos.updateData(vertexPosBuffer);
+
+      vertexTexCoordBuffer.flip();
+      vertexTexCoord.updateData(vertexTexCoordBuffer);
+
+      vertexColorBuffer.flip();
+      vertexColor.updateData(vertexColorBuffer);
+
+      indexBufferBuffer.flip();
+      indexBuffer.updateData(indexBufferBuffer);
+
+      tempMat.loadIdentity();
+      renderManager.setWorldMatrix(tempMat);
+      renderManager.setForcedRenderState(renderState);
+
+      material.setTexture("ColorMap", textureAtlas);
+      material.render(meshGeometry, renderManager);
+    }
+
+    private RenderState.BlendMode convertBlend(final BlendMode blendMode) {
+      if (blendMode == null) {
+          return RenderState.BlendMode.Off;
+      } else if (blendMode == BlendMode.BLEND) {
+          return RenderState.BlendMode.Alpha;
+      } else if (blendMode == BlendMode.MULIPLY) {
+          return RenderState.BlendMode.Modulate;
+      } else {
+          throw new UnsupportedOperationException();
+      }
+  }
+
+    public boolean canAddQuad() {
+      return (quadCount + 1) < BATCH_MAX_QUADS;
+    }
+
+    private void addQuadInternal(
+        final float x,
+        final float y,
+        final float width,
+        final float height,
+        final Color color1,
+        final Color color2,
+        final Color color3,
+        final Color color4,
+        final float textureX,
+        final float textureY,
+        final float textureWidth,
+        final float textureHeight) {
+      indexBufferBuffer.put((short)(globalVertexIndex + 0));
+      indexBufferBuffer.put((short)(globalVertexIndex + 3));
+      indexBufferBuffer.put((short)(globalVertexIndex + 2));
+
+      indexBufferBuffer.put((short)(globalVertexIndex + 0));
+      indexBufferBuffer.put((short)(globalVertexIndex + 2));
+      indexBufferBuffer.put((short)(globalVertexIndex + 1));
+
+      addVertex(x,         y,          textureX,                textureY,                 color1);
+      addVertex(x + width, y,          textureX + textureWidth, textureY,                 color2);
+      addVertex(x + width, y + height, textureX + textureWidth, textureY + textureHeight, color4);
+      addVertex(x,         y + height, textureX,                textureY + textureHeight, color3);
+
+      quadCount++;
+      globalVertexIndex += 4;
+    }
+
+    private void addVertex(final float x, final float y, final float tx, final float ty, final Color c) {
+      vertexPosBuffer.put(x);
+      vertexPosBuffer.put(getHeight() - y);
+      vertexTexCoordBuffer.put(tx);
+      vertexTexCoordBuffer.put(ty);
+      vertexColorBuffer.put(c.getRed());
+      vertexColorBuffer.put(c.getGreen());
+      vertexColorBuffer.put(c.getBlue());
+      vertexColorBuffer.put(c.getAlpha());
+    }
+  }
+}

+ 84 - 16
engine/src/niftygui/com/jme3/niftygui/NiftyJmeDisplay.java

@@ -31,6 +31,9 @@
  */
 package com.jme3.niftygui;
 
+import java.io.InputStream;
+import java.net.URL;
+
 import com.jme3.asset.AssetInfo;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetManager;
@@ -44,11 +47,11 @@ import com.jme3.renderer.Renderer;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.texture.FrameBuffer;
+
 import de.lessvoid.nifty.Nifty;
+import de.lessvoid.nifty.batch.BatchRenderDevice;
 import de.lessvoid.nifty.tools.TimeProvider;
 import de.lessvoid.nifty.tools.resourceloader.ResourceLocation;
-import java.io.InputStream;
-import java.net.URL;
 
 public class NiftyJmeDisplay implements SceneProcessor {
 
@@ -58,6 +61,7 @@ public class NiftyJmeDisplay implements SceneProcessor {
     protected RenderManager renderManager;
     protected InputManager inputManager;
     protected RenderDeviceJme renderDev;
+    protected JmeBatchRenderBackend batchRendererBackend;
     protected InputSystemJme inputSys;
     protected SoundDeviceJme soundDev;
     protected Renderer renderer;
@@ -87,21 +91,71 @@ public class NiftyJmeDisplay implements SceneProcessor {
     //Empty constructor needed for jMP to create replacement input system
     public NiftyJmeDisplay() {
     }
-    
+
+    /**
+     * Create a new NiftyJmeDisplay for use with the Batched Nifty Renderer (improved Nifty rendering performance).
+     *
+     * Nifty will use a single texture of the given dimensions (see atlasWidth and atlasHeight parameters). Every
+     * graphical asset you're rendering through Nifty will be placed into this big texture. The goal is to render
+     * all Nifty components in a single (or at least very few) draw calls. This should speed up rendering quite a
+     * bit.
+     *
+     * Currently you have to make sure to not use more image space than this single texture provides. However, Nifty
+     * tries to be smart about this and internally will make sure that only the images are uploaded that your GUI
+     * really needs. So in general this shoudln't be an issue.
+     *
+     * A complete re-organisation of the texture atlas happens when a Nifty screen ends and another begins. Dynamically
+     * adding images while a screen is running is supported as well.
+     * 
+     * @param assetManager jME AssetManager
+     * @param inputManager jME InputManager
+     * @param audioRenderer jME AudioRenderer
+     * @param viewport Viewport to use
+     * @param atlasWidth the width of the texture atlas Nifty uses to speed up rendering (2048 is a good value)
+     * @param atlasHeight the height of the texture atlas Nifty uses to speed up rendering (2048 is a good value)
+     */
+    public NiftyJmeDisplay(
+        final AssetManager assetManager,
+        final InputManager inputManager,
+        final AudioRenderer audioRenderer,
+        final ViewPort viewport,
+        final int atlasWidth,
+        final int atlasHeight){
+      initialize(assetManager, inputManager, audioRenderer, viewport);
+
+      this.renderDev = null;
+      this.batchRendererBackend = new JmeBatchRenderBackend(this);
+
+      nifty = new Nifty(
+          new BatchRenderDevice(batchRendererBackend, atlasWidth, atlasHeight),
+          soundDev,
+          inputSys,
+          new TimeProvider());
+      inputSys.setNifty(nifty);
+
+      resourceLocation = new ResourceLocationJme();
+      nifty.getResourceLoader().removeAllResourceLocations();
+      nifty.getResourceLoader().addResourceLocation(resourceLocation);
+    }
+
+    /**
+     * Create a standard NiftyJmeDisplay. This uses the old Nifty renderer. It's probably slower then the batched
+     * renderer and is mainly here for backwards compatibility.
+     *
+     * @param assetManager jME AssetManager
+     * @param inputManager jME InputManager
+     * @param audioRenderer jME AudioRenderer
+     * @param viewport Viewport to use
+     */
     public NiftyJmeDisplay(AssetManager assetManager, 
                            InputManager inputManager,
                            AudioRenderer audioRenderer,
                            ViewPort vp){
-        this.assetManager = assetManager;
-        this.inputManager = inputManager;
+        initialize(assetManager, inputManager, audioRenderer, vp);
 
-        w = vp.getCamera().getWidth();
-        h = vp.getCamera().getHeight();
+        this.renderDev = new RenderDeviceJme(this);
+        this.batchRendererBackend = null;
 
-        soundDev = new SoundDeviceJme(assetManager, audioRenderer);
-        renderDev = new RenderDeviceJme(this);
-        inputSys = new InputSystemJme(inputManager);
-        
         nifty = new Nifty(renderDev, soundDev, inputSys, new TimeProvider());
         inputSys.setNifty(nifty);
 
@@ -110,9 +164,27 @@ public class NiftyJmeDisplay implements SceneProcessor {
         nifty.getResourceLoader().addResourceLocation(resourceLocation);
     }
 
+    private void initialize(
+        final AssetManager assetManager,
+        final InputManager inputManager,
+        final AudioRenderer audioRenderer,
+        final ViewPort viewport) {
+      this.assetManager = assetManager;
+      this.inputManager = inputManager;
+      this.w = viewport.getCamera().getWidth();
+      this.h = viewport.getCamera().getHeight();
+      this.soundDev = new SoundDeviceJme(assetManager, audioRenderer);
+      this.inputSys = new InputSystemJme(inputManager);
+    }
+
     public void initialize(RenderManager rm, ViewPort vp) {
         this.renderManager = rm;
-        renderDev.setRenderManager(rm);
+        if (renderDev != null) {
+          renderDev.setRenderManager(rm);
+        } else {
+          batchRendererBackend.setRenderManager(rm);
+        }
+
         if (inputManager != null) {
 //            inputSys.setInputManager(inputManager);
             inputManager.addRawInputListener(inputSys);
@@ -133,10 +205,6 @@ public class NiftyJmeDisplay implements SceneProcessor {
         inputSys.onKeyEvent(event);        
     }
 
-    RenderDeviceJme getRenderDevice() {
-        return renderDev;
-    }
-
     AssetManager getAssetManager() {
         return assetManager;
     }