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
3.0
voi..om 12 years ago
parent f773bbdd38
commit 75569f4c04
  1. 1
      engine/lib/nblibraries.properties
  2. 10
      engine/lib/niftygui/VERSION.txt
  3. BIN
      engine/lib/niftygui/jglfont-core.jar
  4. BIN
      engine/lib/niftygui/nifty-default-controls-javadoc.jar
  5. BIN
      engine/lib/niftygui/nifty-default-controls.jar
  6. BIN
      engine/lib/niftygui/nifty-examples-javadoc.jar
  7. BIN
      engine/lib/niftygui/nifty-examples.jar
  8. BIN
      engine/lib/niftygui/nifty-javadoc.jar
  9. BIN
      engine/lib/niftygui/nifty-style-black.jar
  10. BIN
      engine/lib/niftygui/nifty.jar
  11. 514
      engine/src/niftygui/com/jme3/niftygui/JmeBatchRenderBackend.java
  12. 96
      engine/src/niftygui/com/jme3/niftygui/NiftyJmeDisplay.java

@ -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!//;\

@ -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

Binary file not shown.

@ -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());
}
}
}

@ -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;
@ -88,19 +92,69 @@ public class NiftyJmeDisplay implements SceneProcessor {
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();
soundDev = new SoundDeviceJme(assetManager, audioRenderer);
renderDev = new RenderDeviceJme(this);
inputSys = new InputSystemJme(inputManager);
this.renderDev = new RenderDeviceJme(this);
this.batchRendererBackend = null;
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;
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;
}

Loading…
Cancel
Save