From bee759bddc470baf5c8f1962b8fae2055a13a355 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Fri, 21 Aug 2015 20:32:27 -0400 Subject: [PATCH 1/4] GLRenderer: initial VAO support (still buggy) --- .../com/jme3/renderer/opengl/GLRenderer.java | 398 +++++++++++------- 1 file changed, 251 insertions(+), 147 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index dd30da089..83644bcaa 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -2284,33 +2284,38 @@ public class GLRenderer implements Renderer { } context.attribIndexList.copyNewToOld(); } - - public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { - if (vb.getBufferType() == VertexBuffer.Type.Index) { - throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); - } - - if (context.boundShaderProgram <= 0) { - throw new IllegalStateException("Cannot render mesh without shader bound"); - } - - Attribute attrib = context.boundShader.getAttribute(vb.getBufferType()); + + private int updateAttributeLocation(Shader shader, VertexBuffer.Type attribType) { + Attribute attrib = shader.getAttribute(attribType); int loc = attrib.getLocation(); if (loc == -1) { - return; // not defined + return -1; // not defined } if (loc == -2) { - loc = gl.glGetAttribLocation(context.boundShaderProgram, "in" + vb.getBufferType().name()); + loc = gl.glGetAttribLocation(context.boundShaderProgram, "in" + attribType.name()); // not really the name of it in the shader (inPosition) but // the internal name of the enum (Position). if (loc < 0) { attrib.setLocation(-1); - return; // not available in shader. + return -1; // not available in shader. } else { attrib.setLocation(loc); } } + return loc; + } + + public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { + if (vb.getBufferType() == VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); + } + + Shader shader = context.boundShader; + int location = updateAttributeLocation(shader, vb.getBufferType()); + if (location == -1) { + return; + } if (vb.isInstanced()) { if (!caps.contains(Caps.MeshInstancing)) { @@ -2334,11 +2339,11 @@ public class GLRenderer implements Renderer { VertexBuffer[] attribs = context.boundAttribs; for (int i = 0; i < slotsRequired; i++) { - if (!context.attribIndexList.moveToNew(loc + i)) { - gl.glEnableVertexAttribArray(loc + i); + if (!context.attribIndexList.moveToNew(location + i)) { + gl.glEnableVertexAttribArray(location + i); } } - if (attribs[loc] != vb) { + if (attribs[location] != vb) { // NOTE: Use id from interleaved buffer if specified int bufId = idb != null ? idb.getId() : vb.getId(); assert bufId != -1; @@ -2351,12 +2356,12 @@ public class GLRenderer implements Renderer { } if (slotsRequired == 1) { - gl.glVertexAttribPointer(loc, - vb.getNumComponents(), - convertFormat(vb.getFormat()), - vb.isNormalized(), - vb.getStride(), - vb.getOffset()); + gl.glVertexAttribPointer(location, + vb.getNumComponents(), + convertFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + vb.getOffset()); } else { for (int i = 0; i < slotsRequired; i++) { // The pointer maps the next 4 floats in the slot. @@ -2367,17 +2372,17 @@ public class GLRenderer implements Renderer { // P4: ____________XXXX____________XXXX // stride = 4 bytes in float * 4 floats in slot * num slots // offset = 4 bytes in float * 4 floats in slot * slot index - gl.glVertexAttribPointer(loc + i, - 4, - convertFormat(vb.getFormat()), - vb.isNormalized(), - 4 * 4 * slotsRequired, - 4 * 4 * i); + gl.glVertexAttribPointer(location + i, + 4, + convertFormat(vb.getFormat()), + vb.isNormalized(), + 4 * 4 * slotsRequired, + 4 * 4 * i); } } for (int i = 0; i < slotsRequired; i++) { - int slot = loc + i; + int slot = location + i; if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) { // non-instanced -> instanced glext.glVertexAttribDivisorARB(slot, vb.getInstanceSpan()); @@ -2390,6 +2395,92 @@ public class GLRenderer implements Renderer { } } + /** + * Set VBO on VAO. Assumes a brand new mesh or modified mesh with new buffer. + * + * @param vb + * @param idb + */ + public void setVertexAttribVAO(VertexBuffer vb, VertexBuffer idb) { + if (vb.getBufferType() == VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); + } + + Shader shader = context.boundShader; + int location = updateAttributeLocation(shader, vb.getBufferType()); + if (location == -1) { + return; + } + + if (vb.isInstanced()) { + if (!caps.contains(Caps.MeshInstancing)) { + throw new RendererException("Instancing is required, " + + "but not supported by the " + + "graphics hardware"); + } + } + int slotsRequired = 1; + if (vb.getNumComponents() > 4) { + if (vb.getNumComponents() % 4 != 0) { + throw new RendererException("Number of components in multi-slot " + + "buffers must be divisible by 4"); + } + slotsRequired = vb.getNumComponents() / 4; + } + + if (vb.isUpdateNeeded() && idb == null) { + updateBufferData(vb); + } + + for (int i = 0; i < slotsRequired; i++) { + gl.glEnableVertexAttribArray(location + i); + } + + // NOTE: Use id from interleaved buffer if specified + int bufId = idb != null ? idb.getId() : vb.getId(); + assert bufId != -1; + if (context.boundArrayVBO != bufId) { + gl.glBindBuffer(GL.GL_ARRAY_BUFFER, bufId); + context.boundArrayVBO = bufId; + //statistics.onVertexBufferUse(vb, true); + } else { + //statistics.onVertexBufferUse(vb, false); + } + + if (slotsRequired == 1) { + gl.glVertexAttribPointer(location, + vb.getNumComponents(), + convertFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + vb.getOffset()); + } else { + for (int i = 0; i < slotsRequired; i++) { + // The pointer maps the next 4 floats in the slot. + // E.g. + // P1: XXXX____________XXXX____________ + // P2: ____XXXX____________XXXX________ + // P3: ________XXXX____________XXXX____ + // P4: ____________XXXX____________XXXX + // stride = 4 bytes in float * 4 floats in slot * num slots + // offset = 4 bytes in float * 4 floats in slot * slot index + gl.glVertexAttribPointer(location + i, + 4, + convertFormat(vb.getFormat()), + vb.isNormalized(), + 4 * 4 * slotsRequired, + 4 * 4 * i); + } + } + + for (int i = 0; i < slotsRequired; i++) { + int slot = location + i; + if (vb.isInstanced()) { + glext.glVertexAttribDivisorARB(slot, vb.getInstanceSpan()); + } + } + } + public void setVertexAttrib(VertexBuffer vb) { setVertexAttrib(vb, null); } @@ -2442,57 +2533,19 @@ public class GLRenderer implements Renderer { int vertCount = mesh.getVertexCount(); boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); - if (mesh.getMode() == Mode.Hybrid) { - int[] modeStart = mesh.getModeStart(); - int[] elementLengths = mesh.getElementLengths(); - - int elMode = convertElementMode(Mode.Triangles); - int fmt = convertFormat(indexBuf.getFormat()); - int elSize = indexBuf.getFormat().getComponentSize(); - int listStart = modeStart[0]; - int stripStart = modeStart[1]; - int fanStart = modeStart[2]; - int curOffset = 0; - for (int i = 0; i < elementLengths.length; i++) { - if (i == stripStart) { - elMode = convertElementMode(Mode.TriangleStrip); - } else if (i == fanStart) { - elMode = convertElementMode(Mode.TriangleFan); - } - int elementLength = elementLengths[i]; - - if (useInstancing) { - glext.glDrawElementsInstancedARB(elMode, - elementLength, - fmt, - curOffset, - count); - } else { - gl.glDrawRangeElements(elMode, - 0, - vertCount, - elementLength, - fmt, - curOffset); - } - - curOffset += elementLength * elSize; - } + if (useInstancing) { + glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()), + indexBuf.getData().limit(), + convertFormat(indexBuf.getFormat()), + 0, + count); } else { - if (useInstancing) { - glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()), - indexBuf.getData().limit(), - convertFormat(indexBuf.getFormat()), - 0, - count); - } else { - gl.glDrawRangeElements(convertElementMode(mesh.getMode()), - 0, - vertCount, - indexBuf.getData().limit(), - convertFormat(indexBuf.getFormat()), - 0); - } + gl.glDrawRangeElements(convertElementMode(mesh.getMode()), + 0, + vertCount, + indexBuf.getData().limit(), + convertFormat(indexBuf.getFormat()), + 0); } } @@ -2521,30 +2574,19 @@ public class GLRenderer implements Renderer { throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); } } - - public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) { - int id = mesh.getId(); - if (id == -1) { - IntBuffer temp = intBuf1; - gl3.glGenVertexArrays(temp); - id = temp.get(0); - mesh.setId(id); - } - - if (context.boundVertexArray != id) { - gl3.glBindVertexArray(id); - context.boundVertexArray = id; - } - + + private void setupVertexBuffersLegacy(Mesh mesh, VertexBuffer[] instanceData) { VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); if (interleavedData != null && interleavedData.isUpdateNeeded()) { updateBufferData(interleavedData); } if (instanceData != null) { - setVertexAttrib(instanceData, null); + for (VertexBuffer vb : instanceData) { + setVertexAttrib(vb, null); + } } - + for (VertexBuffer vb : mesh.getBufferList().getArray()) { if (vb.getBufferType() == Type.InterleavedData || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers @@ -2561,74 +2603,118 @@ public class GLRenderer implements Renderer { } } } - - private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) { - if (mesh.getId() == -1) { - updateVertexArray(mesh, instanceData); - } else { - // TODO: Check if it was updated - } - - if (context.boundVertexArray != mesh.getId()) { - gl3.glBindVertexArray(mesh.getId()); - context.boundVertexArray = mesh.getId(); + + private void setupVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) { + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (instanceData != null) { + for (VertexBuffer vb : instanceData) { + setVertexAttribVAO(vb, null); + } } + + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } -// IntMap buffers = mesh.getBuffers(); - VertexBuffer indices; - if (mesh.getNumLodLevels() > 0) { - indices = mesh.getLodLevel(lod); - } else { - indices = mesh.getBuffer(Type.Index); - } - if (indices != null) { - drawTriangleList(indices, mesh, count); - } else { - drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount()); + if (vb.getStride() == 0) { + // not interleaved + setVertexAttribVAO(vb, null); + } else { + // interleaved + setVertexAttribVAO(vb, interleavedData); + } } - clearVertexAttribs(); + + mesh.clearUpdateNeeded(); } - private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { - - // Here while count is still passed in. Can be removed when/if - // the method is collapsed again. -pspeed - count = Math.max(mesh.getInstanceCount(), count); - + private void updateVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) { VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); if (interleavedData != null && interleavedData.isUpdateNeeded()) { updateBufferData(interleavedData); } - + if (instanceData != null) { + for (VertexBuffer vb : instanceData) { + if (vb.isUpdateNeeded()) { + updateBufferData(vb); + } + } + } + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index + || !vb.isUpdateNeeded() + || !context.boundShader.isAttributeDefined(vb.getBufferType())) { + continue; + } + updateBufferData(vb); + } + } + + private VertexBuffer getIndexBuffer(Mesh mesh, int lod) { VertexBuffer indices; if (mesh.getNumLodLevels() > 0) { indices = mesh.getLodLevel(lod); } else { indices = mesh.getBuffer(Type.Index); } + return indices; + } - if (instanceData != null) { - for (VertexBuffer vb : instanceData) { - setVertexAttrib(vb, null); - } + private void setVertexArrayObject(Mesh mesh) { + int id = mesh.getId(); + + if (id == -1) { + IntBuffer temp = intBuf1; + gl3.glGenVertexArrays(temp); + id = temp.get(0); + mesh.setId(id); + + objManager.registerObject(mesh); } - for (VertexBuffer vb : mesh.getBufferList().getArray()) { - if (vb.getBufferType() == Type.InterleavedData - || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers - || vb.getBufferType() == Type.Index) { - continue; + if (context.boundVertexArray != id) { + gl3.glBindVertexArray(id); + context.boundVertexArray = id; + } + } + + private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { + setVertexArrayObject(mesh); + + VertexBuffer indices = getIndexBuffer(mesh, lod); + if (mesh.isUpdateNeeded()) { + setupVertexBuffers(mesh, instanceData); + updateBufferData(indices); + } else { + updateVertexBuffers(mesh, instanceData); + if (indices != null) { + // NOTE: context.boundElementArrayVBO gets captured in the VAO. + // Make everyone think its already bound. + context.boundElementArrayVBO = indices.getId(); } + } - if (vb.getStride() == 0) { - // not interleaved - setVertexAttrib(vb); - } else { - // interleaved - setVertexAttrib(vb, interleavedData); + if (indices != null) { + if (indices.isUpdateNeeded()) { + updateBufferData(indices); } + + drawTriangleList(indices, mesh, count); + + context.boundElementArrayVBO = 0; + } else { + drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount()); } + } + private void renderMeshLegacy(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { + setupVertexBuffersLegacy(mesh, instanceData); + VertexBuffer indices = getIndexBuffer(mesh, lod); if (indices != null) { drawTriangleList(indices, mesh, count); } else { @@ -2651,14 +2737,32 @@ public class GLRenderer implements Renderer { if (gl4 != null && mesh.getMode().equals(Mode.Patch)) { gl4.glPatchParameter(mesh.getPatchVertexCount()); } + statistics.onMeshDrawn(mesh, lod, count); -// if (ctxCaps.GL_ARB_vertex_array_object){ -// renderMeshVertexArray(mesh, lod, count); -// }else{ - renderMeshDefault(mesh, lod, count, instanceData); -// } + + // Here while count is still passed in. Can be removed when/if + // the method is collapsed again. -pspeed + count = Math.max(mesh.getInstanceCount(), count); + +// if (caps.contains(Caps.VertexBufferArray)) { + renderMeshDefault(mesh, lod, count, instanceData); +// } else { +// renderMeshLegacy(mesh, lod, count, instanceData); +// // } } + @Override + public void deleteMesh(Mesh mesh) { + int bufId = mesh.getId(); + if (bufId != -1) { + // delete vertex array object + intBuf1.put(0, bufId); + intBuf1.position(0).limit(1); + gl3.glDeleteVertexArrays(intBuf1); + mesh.resetObject(); + } + } + public void setMainFrameBufferSrgb(boolean enableSrgb) { // Gamma correction if (!caps.contains(Caps.Srgb) && enableSrgb) { From e8f344a0dbcef7578544a57757fdd3439c3cf4c5 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Fri, 21 Aug 2015 21:21:31 -0400 Subject: [PATCH 2/4] GLRenderer: remaining portion of VAO support --- .../java/com/jme3/font/BitmapTextPage.java | 5 + .../main/java/com/jme3/renderer/Renderer.java | 6 + .../src/main/java/com/jme3/scene/Mesh.java | 199 ++++++------------ .../java/com/jme3/scene/VertexBuffer.java | 17 +- .../src/main/java/com/jme3/shader/Shader.java | 10 + .../java/com/jme3/system/NullRenderer.java | 3 + .../main/java/com/jme3/util/NativeObject.java | 3 +- 7 files changed, 108 insertions(+), 135 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java index 1edac967c..d0d2e6969 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java @@ -137,6 +137,11 @@ class BitmapTextPage extends Geometry { Mesh m = getMesh(); int vertCount = pageQuads.size() * 4; int triCount = pageQuads.size() * 2; + + if (vertCount > m.getVertexCount() || + triCount > m.getTriangleCount()) { + m.setUpdateNeeded(); + } VertexBuffer pb = m.getBuffer(Type.Position); VertexBuffer tb = m.getBuffer(Type.TexCoord); diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java index edfd380b4..1784eb7f5 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java @@ -283,6 +283,12 @@ public interface Renderer { * the per-instance attributes. */ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData); + + /** + * Delete a Mesh (or Vertex Array Object in GL terms) from the GPU. + * @param mesh The mesh to delete. + */ + public void deleteMesh(Mesh mesh); /** * Resets all previously used {@link NativeObject Native Objects} on this Renderer. diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java index a0f8e1fe6..fbe5d7794 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -42,6 +42,7 @@ import com.jme3.math.Matrix4f; import com.jme3.math.Triangle; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; +import com.jme3.renderer.Renderer; import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; @@ -49,6 +50,7 @@ import com.jme3.scene.mesh.*; import com.jme3.util.BufferUtils; import com.jme3.util.IntMap; import com.jme3.util.IntMap.Entry; +import com.jme3.util.NativeObject; import com.jme3.util.SafeArrayList; import java.io.IOException; import java.nio.*; @@ -71,7 +73,7 @@ import java.util.ArrayList; * * @author Kirill Vainer */ -public class Mesh implements Savable, Cloneable { +public class Mesh extends NativeObject implements Savable { /** * The mode of the Mesh specifies both the type of primitive represented @@ -127,19 +129,14 @@ public class Mesh implements Savable, Cloneable { */ TriangleFan(false), - /** - * A combination of various triangle modes. It is best to avoid - * using this mode as it may not be supported by all renderers. - * The {@link Mesh#setModeStart(int[]) mode start points} and - * {@link Mesh#setElementLengths(int[]) element lengths} must - * be specified for this mode. - */ - Hybrid(false), + Reserved(false), + /** * Used for Tesselation only. Requires to set the number of vertices * for each patch (default is 3 for triangle tesselation) */ Patch(true); + private boolean listMode = false; private Mode(boolean listMode){ @@ -182,9 +179,6 @@ public class Mesh implements Savable, Cloneable { private int patchVertexCount=3; //only used for tesselation private int maxNumWeights = -1; // only if using skeletal animation - private int[] elementLengths; - private int[] modeStart; - private Mode mode = Mode.Triangles; /** @@ -193,6 +187,10 @@ public class Mesh implements Savable, Cloneable { public Mesh(){ } + protected Mesh(int id) { + super(id); + } + /** * Create a shallow clone of this Mesh. The {@link VertexBuffer vertex * buffers} are shared between this and the clone mesh, the rest @@ -202,23 +200,12 @@ public class Mesh implements Savable, Cloneable { */ @Override public Mesh clone() { - try { - Mesh clone = (Mesh) super.clone(); - clone.meshBound = meshBound.clone(); - clone.collisionTree = collisionTree != null ? collisionTree : null; - clone.buffers = buffers.clone(); - clone.buffersList = new SafeArrayList(VertexBuffer.class,buffersList); - clone.vertexArrayID = -1; - if (elementLengths != null) { - clone.elementLengths = elementLengths.clone(); - } - if (modeStart != null) { - clone.modeStart = modeStart.clone(); - } - return clone; - } catch (CloneNotSupportedException ex) { - throw new AssertionError(); - } + Mesh clone = (Mesh) super.clone(); + clone.meshBound = meshBound.clone(); + clone.collisionTree = collisionTree != null ? collisionTree : null; + clone.buffers = buffers.clone(); + clone.buffersList = new SafeArrayList(VertexBuffer.class,buffersList); + return clone; } /** @@ -229,37 +216,30 @@ public class Mesh implements Savable, Cloneable { * @return a deep clone of this mesh. */ public Mesh deepClone(){ - try{ - Mesh clone = (Mesh) super.clone(); - clone.meshBound = meshBound != null ? meshBound.clone() : null; - - // TODO: Collision tree cloning - //clone.collisionTree = collisionTree != null ? collisionTree : null; - clone.collisionTree = null; // it will get re-generated in any case - - clone.buffers = new IntMap(); - clone.buffersList = new SafeArrayList(VertexBuffer.class); - for (VertexBuffer vb : buffersList.getArray()){ - VertexBuffer bufClone = vb.clone(); - clone.buffers.put(vb.getBufferType().ordinal(), bufClone); - clone.buffersList.add(bufClone); - } - - clone.vertexArrayID = -1; - clone.vertCount = vertCount; - clone.elementCount = elementCount; - clone.instanceCount = instanceCount; - - // although this could change - // if the bone weight/index buffers are modified - clone.maxNumWeights = maxNumWeights; - - clone.elementLengths = elementLengths != null ? elementLengths.clone() : null; - clone.modeStart = modeStart != null ? modeStart.clone() : null; - return clone; - }catch (CloneNotSupportedException ex){ - throw new AssertionError(); + Mesh clone = (Mesh) super.clone(); + clone.meshBound = meshBound != null ? meshBound.clone() : null; + + // TODO: Collision tree cloning + //clone.collisionTree = collisionTree != null ? collisionTree : null; + clone.collisionTree = null; // it will get re-generated in any case + + clone.buffers = new IntMap(); + clone.buffersList = new SafeArrayList(VertexBuffer.class); + for (VertexBuffer vb : buffersList.getArray()){ + VertexBuffer bufClone = vb.clone(); + clone.buffers.put(vb.getBufferType().ordinal(), bufClone); + clone.buffersList.add(bufClone); } + + clone.vertCount = vertCount; + clone.elementCount = elementCount; + clone.instanceCount = instanceCount; + + // although this could change + // if the bone weight/index buffers are modified + clone.maxNumWeights = maxNumWeights; + + return clone; } /** @@ -475,40 +455,6 @@ public class Mesh implements Savable, Cloneable { return lodLevels[lod]; } - /** - * Get the element lengths for {@link Mode#Hybrid} mesh mode. - * - * @return element lengths - */ - public int[] getElementLengths() { - return elementLengths; - } - - /** - * Set the element lengths for {@link Mode#Hybrid} mesh mode. - * - * @param elementLengths The element lengths to set - */ - public void setElementLengths(int[] elementLengths) { - this.elementLengths = elementLengths; - } - - /** - * Set the mode start indices for {@link Mode#Hybrid} mesh mode. - * - * @return mode start indices - */ - public int[] getModeStart() { - return modeStart; - } - - /** - * Get the mode start indices for {@link Mode#Hybrid} mesh mode. - */ - public void setModeStart(int[] modeStart) { - this.modeStart = modeStart; - } - /** * Returns the mesh mode * @@ -899,23 +845,6 @@ public class Mesh implements Savable, Cloneable { indices[2] = ib.get(vertIndex+2); } - /** - * Returns the mesh's VAO ID. Internal use only. - */ - public int getId(){ - return vertexArrayID; - } - - /** - * Sets the mesh's VAO ID. Internal use only. - */ - public void setId(int id){ - if (vertexArrayID != -1) - throw new IllegalStateException("ID has already been set."); - - vertexArrayID = id; - } - /** * Generates a collision tree for the mesh. * Called automatically by {@link #collideWith(com.jme3.collision.Collidable, @@ -1109,20 +1038,17 @@ public class Mesh implements Savable, Cloneable { * @return A virtual or wrapped index buffer to read the data as a list */ public IndexBuffer getIndicesAsList(){ - if (mode == Mode.Hybrid) - throw new UnsupportedOperationException("Hybrid mode not supported"); - IndexBuffer ib = getIndexBuffer(); - if (ib != null){ - if (mode.isListMode()){ + if (ib != null) { + if (mode.isListMode()) { // already in list mode - return ib; - }else{ + return ib; + } else { // not in list mode but it does have an index buffer // wrap it so the data is converted to list format return new WrappedIndexBuffer(this); } - }else{ + } else { // return a virtual index buffer that will supply // "fake" indices in list format return new VirtualIndexBuffer(vertCount, mode); @@ -1385,16 +1311,30 @@ public class Mesh implements Savable, Cloneable { return patchVertexCount; } + @Override + public void resetObject() { + id = -1; + setUpdateNeeded(); + } + + @Override + public void deleteObject(Object rendererObject) { + ((Renderer)rendererObject).deleteMesh(this); + } + + @Override + public NativeObject createDestructableClone() { + return new Mesh(id); + } + + @Override + public long getUniqueId() { + return ((long)OBJTYPE_MESH << 32) | ((long)id); + } + public void write(JmeExporter ex) throws IOException { OutputCapsule out = ex.getCapsule(this); -// HashMap map = new HashMap(); -// for (Entry buf : buffers){ -// if (buf.getValue() != null) -// map.put(buf.getKey()+"a", buf.getValue()); -// } -// out.writeStringSavableMap(map, "buffers", null); - out.write(meshBound, "modelBound", null); out.write(vertCount, "vertCount", -1); out.write(elementCount, "elementCount", -1); @@ -1402,8 +1342,6 @@ public class Mesh implements Savable, Cloneable { out.write(maxNumWeights, "max_num_weights", -1); out.write(mode, "mode", Mode.Triangles); out.write(collisionTree, "collisionTree", null); - out.write(elementLengths, "elementLengths", null); - out.write(modeStart, "modeStart", null); out.write(pointSize, "pointSize", 1f); //Removing HW skinning buffers to not save them @@ -1439,21 +1377,16 @@ public class Mesh implements Savable, Cloneable { instanceCount = in.readInt("instanceCount", -1); maxNumWeights = in.readInt("max_num_weights", -1); mode = in.readEnum("mode", Mode.class, Mode.Triangles); - elementLengths = in.readIntArray("elementLengths", null); - modeStart = in.readIntArray("modeStart", null); collisionTree = (BIHTree) in.readSavable("collisionTree", null); - elementLengths = in.readIntArray("elementLengths", null); - modeStart = in.readIntArray("modeStart", null); pointSize = in.readFloat("pointSize", 1f); -// in.readStringSavableMap("buffers", null); buffers = (IntMap) in.readIntSavableMap("buffers", null); for (Entry entry : buffers){ buffersList.add(entry.getValue()); } //creating hw animation buffers empty so that they are put in the cache - if(isAnimated()){ + if (isAnimated()) { VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex); hwBoneIndex.setUsage(Usage.CpuOnly); setBuffer(hwBoneIndex); diff --git a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java index c67435b78..3fe8e5cad 100644 --- a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java @@ -524,6 +524,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { this.usage = usage; } + /** + * The size of an element in bytes. + * + * The number of components multiplied by the size of a component. + * + * @return size of an element in bytes. + */ + public int getElementSize() { + return componentsLength; + } + /** * @param normalized Set to true if integer components should be converted * from their maximal range into the range 0.0 - 1.0 when converted to @@ -976,6 +987,10 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { * of the parameters. The buffer will be of the type specified by * {@link Format format} and would be able to contain the given number * of elements with the given number of components in each element. + * @param format The format of the buffer to create + * @param components The number of components (aka dimensions) + * @param numElements Capacity of the buffer in number of elements. + * @return A buffer satisfying the given requirements. */ public static Buffer createBuffer(Format format, int components, int numElements){ if (components < 1 || components > 4) @@ -1010,7 +1025,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { * @return Deep clone of this buffer */ @Override - public VertexBuffer clone(){ + public VertexBuffer clone() { // NOTE: Superclass GLObject automatically creates shallow clone // e.g re-use ID. VertexBuffer vb = (VertexBuffer) super.clone(); diff --git a/jme3-core/src/main/java/com/jme3/shader/Shader.java b/jme3-core/src/main/java/com/jme3/shader/Shader.java index eb084f178..cfe915ad8 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Shader.java +++ b/jme3-core/src/main/java/com/jme3/shader/Shader.java @@ -274,6 +274,16 @@ public final class Shader extends NativeObject { return attrib; } + public boolean isAttributeDefined(VertexBuffer.Type attribType) { + int ordinal = attribType.ordinal(); + Attribute attrib = attribs.get(ordinal); + if (attrib == null){ + return false; + } else { + return attrib.location != -1 && attrib.location != 2; + } + } + public ListMap getUniformMap(){ return uniforms; } diff --git a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java index f2e029af4..7ac0b3495 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java +++ b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java @@ -164,4 +164,7 @@ public class NullRenderer implements Renderer { public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { } + @Override + public void deleteMesh(Mesh mesh) { + } } diff --git a/jme3-core/src/main/java/com/jme3/util/NativeObject.java b/jme3-core/src/main/java/com/jme3/util/NativeObject.java index 508e6623d..34114ebd2 100644 --- a/jme3-core/src/main/java/com/jme3/util/NativeObject.java +++ b/jme3-core/src/main/java/com/jme3/util/NativeObject.java @@ -52,7 +52,8 @@ public abstract class NativeObject implements Cloneable { OBJTYPE_SHADERSOURCE = 5, OBJTYPE_AUDIOBUFFER = 6, OBJTYPE_AUDIOSTREAM = 7, - OBJTYPE_FILTER = 8; + OBJTYPE_FILTER = 8, + OBJTYPE_MESH = 9; /** * The object manager to which this NativeObject is registered to. From 860de88298d2dcb5c38079b3279511704e3ba897 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Fri, 21 Aug 2015 21:27:54 -0400 Subject: [PATCH 3/4] GLRenderer: initial commit of async FB read (including jme panels) --- .../renderer/opengl/AsyncFrameReader.java | 280 +++++++++++++ .../opengl/FrameBufferReadRequest.java | 98 +++++ .../java/com/jme3/system/awt/AwtPanel.java | 270 +++++++++---- .../com/jme3/system/awt/AwtPanelsContext.java | 32 +- .../java/com/jme3/system/awt/JmePanel.java | 48 +++ .../java/com/jme3/system/awt/SwingPanel.java | 382 ++++++++++++++++++ 6 files changed, 1014 insertions(+), 96 deletions(-) create mode 100755 jme3-core/src/main/java/com/jme3/renderer/opengl/AsyncFrameReader.java create mode 100755 jme3-core/src/main/java/com/jme3/renderer/opengl/FrameBufferReadRequest.java create mode 100755 jme3-desktop/src/main/java/com/jme3/system/awt/JmePanel.java create mode 100755 jme3-desktop/src/main/java/com/jme3/system/awt/SwingPanel.java diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/AsyncFrameReader.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/AsyncFrameReader.java new file mode 100755 index 000000000..3b41c6b41 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/AsyncFrameReader.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2009-2015 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.renderer.opengl; + +import com.jme3.renderer.RenderContext; +import com.jme3.renderer.RendererException; +import com.jme3.texture.FrameBuffer; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * @author Kirill Vainer + */ +final class AsyncFrameReader { + + private final ArrayList pboPool = new ArrayList(); + private final List pending = Collections.synchronizedList(new ArrayList()); + private final GLRenderer renderer; + private final GL gl; + private final GLExt glext; + private final IntBuffer intBuf = BufferUtils.createIntBuffer(1); + private final RenderContext context; + private final Thread glThread; + + AsyncFrameReader(GLRenderer renderer, GL gl, GLExt glext, RenderContext context) { + this.renderer = renderer; + this.gl = gl; + this.glext = glext; + this.context = context; + this.glThread = Thread.currentThread(); + } + + private PixelBuffer acquirePixelBuffer(int dataSize) { + PixelBuffer pb; + + if (pboPool.isEmpty()) { + // create PBO + pb = new PixelBuffer(); + intBuf.clear(); + gl.glGenBuffers(intBuf); + pb.id = intBuf.get(0); + } else { + // reuse PBO. + pb = pboPool.remove(pboPool.size() - 1); + } + + // resize or allocate PBO if required. + if (pb.size != dataSize) { + if (context.boundPixelPackPBO != pb.id) { + gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, pb.id); + context.boundPixelPackPBO = pb.id; + } + gl.glBufferData(GLExt.GL_PIXEL_PACK_BUFFER_ARB, dataSize, GL.GL_STREAM_READ); + } + + pb.size = dataSize; + + return pb; + } + + private void readFrameBufferFromPBO(FrameBufferReadRequest fbrr) { + // assumes waitForCompletion was already called! + if (context.boundPixelPackPBO != fbrr.pb.id) { + gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, fbrr.pb.id); + context.boundPixelPackPBO = fbrr.pb.id; + } + gl.glGetBufferSubData(GLExt.GL_PIXEL_PACK_BUFFER_ARB, 0, fbrr.targetBuf); + } + + private boolean waitForCompletion(FrameBufferReadRequest fbrr, long time, TimeUnit unit, boolean flush) { + int flags = flush ? GLExt.GL_SYNC_FLUSH_COMMANDS_BIT : 0; + long nanos = unit.toNanos(time); + switch (glext.glClientWaitSync(fbrr.fence, flags, nanos)) { + case GLExt.GL_ALREADY_SIGNALED: + case GLExt.GL_CONDITION_SATISFIED: + return true; + case GLExt.GL_TIMEOUT_EXPIRED: + return false; + case GLExt.GL_WAIT_FAILED: + throw new RendererException("Waiting for fence failed"); + default: + throw new RendererException("Unexpected result from glClientWaitSync"); + } + } + + private void signalFinished(FrameBufferReadRequest fbrr) { + fbrr.lock.lock(); + try { + fbrr.done = true; + fbrr.cond.signalAll(); + } finally { + fbrr.lock.unlock(); + } + } + + void signalCancelled(FrameBufferReadRequest fbrr) { + fbrr.lock.lock(); + try { + fbrr.cancelled = true; + fbrr.cond.signalAll(); + } finally { + fbrr.lock.unlock(); + } + } + + public void updateReadRequests() { + // Update requests in the order they were made (e.g. earliest first) + for (Iterator it = pending.iterator(); it.hasNext();) { + FrameBufferReadRequest fbrr = it.next(); + + // Check status for the user... (non-blocking) + if (!fbrr.cancelled && !fbrr.done) { + // Request a flush if we know clients are waiting + // (to speed up the process, or make it take finite time ..) + boolean flush = false; // fbrr.clientsWaiting.get() > 0; + if (waitForCompletion(fbrr, 0, TimeUnit.NANOSECONDS, flush)) { + if (!fbrr.cancelled) { + // Operation completed. + // Read data into user's ByteBuffer + readFrameBufferFromPBO(fbrr); + + // Signal any waiting threads that we are done. + // Also, set the done flag. + signalFinished(fbrr); + } + } + } + + if (fbrr.cancelled || fbrr.done) { + // Cleanup + // Return the pixel buffer back into the pool. + if (!pboPool.contains(fbrr.pb)) { + pboPool.add(fbrr.pb); + } + + // Remove this request from the pending requests list. + it.remove(); + + // Get rid of the fence + glext.glDeleteSync(fbrr.fence); + + fbrr.pb = null; + fbrr.fence = null; + } + } + } + + ByteBuffer getFrameBufferData(FrameBufferReadRequest fbrr, long time, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + + if (fbrr.cancelled) { + throw new CancellationException(); + } + + if (fbrr.done) { + return fbrr.targetBuf; + } + + if (glThread == Thread.currentThread()) { + // Running on GL thread, hence can use GL commands .. + try { + // Wait until we reach the fence.. + + // PROBLEM: if the user is holding any locks, + // they will not be released here, + // causing a potential deadlock! + if (!waitForCompletion(fbrr, time, unit, true)) { + throw new TimeoutException(); + } + + // Command stream reached this point. + if (fbrr.cancelled) { + // User not interested in this anymore. + throw new CancellationException(); + } else { + // Read data into user's ByteBuffer + readFrameBufferFromPBO(fbrr); + } + + // Mark it as done, so future get() calls always return. + signalFinished(fbrr); + + return fbrr.targetBuf; + } catch (RendererException ex) { + throw new ExecutionException(ex); + } + } else { + long nanos = unit.toNanos(time); + + fbrr.lock.lock(); + try { + // Not running on GL thread, indicate that we are running + // so GL thread can request GPU to finish quicker ... + fbrr.clientsWaiting.getAndIncrement(); + + // Wait until we finish + while (!fbrr.done && !fbrr.cancelled) { + if (nanos <= 0L) { + throw new TimeoutException(); + } + + nanos = fbrr.cond.awaitNanos(nanos); + } + + if (fbrr.cancelled) { + throw new CancellationException(); + } + + return fbrr.targetBuf; + } finally { + fbrr.lock.unlock(); + fbrr.clientsWaiting.getAndDecrement(); + } + } + } + + public Future readFrameBufferLater(FrameBuffer fb, ByteBuffer byteBuf) { + // Create & allocate a PBO (or reuse an existing one if available) + FrameBufferReadRequest fbrr = new FrameBufferReadRequest(this); + fbrr.targetBuf = byteBuf; + + int desiredSize = fb.getWidth() * fb.getHeight() * 4; + + if (byteBuf.remaining() != desiredSize) { + throw new IllegalArgumentException("Ensure buffer size matches framebuffer size"); + } + + fbrr.pb = acquirePixelBuffer(desiredSize); + + // Read into PBO (asynchronous) +// renderer.readFrameBufferWithGLFormat(fb, null, GL2.GL_BGRA, GL2.GL_UNSIGNED_BYTE, fbrr.pb.id); + + // Insert fence into command stream. + fbrr.fence = glext.glFenceSync(GLExt.GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + + // Insert into FIFO + pending.add(fbrr); + + return fbrr; + } +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/FrameBufferReadRequest.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/FrameBufferReadRequest.java new file mode 100755 index 000000000..24021cdb3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/FrameBufferReadRequest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2009-2015 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.renderer.opengl; + +import java.nio.ByteBuffer; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +class PixelBuffer { + int id = -1; + int size = -1; +} + +class FrameBufferReadRequest implements Future { + + AsyncFrameReader reader; + Object fence; + PixelBuffer pb; + ByteBuffer targetBuf; + boolean cancelled; + boolean done; + + final ReentrantLock lock = new ReentrantLock(); + final Condition cond = lock.newCondition(); + final AtomicInteger clientsWaiting = new AtomicInteger(0); + + public FrameBufferReadRequest(AsyncFrameReader reader) { + this.reader = reader; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + if (isDone()) { + return false; + } + reader.signalCancelled(this); + return true; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public boolean isDone() { + return done; + } + + @Override + public ByteBuffer get(long l, TimeUnit tu) throws InterruptedException, ExecutionException, TimeoutException { + return reader.getFrameBufferData(this, l, tu); + } + + @Override + public ByteBuffer get() throws InterruptedException, ExecutionException { + try { + return get(1, TimeUnit.SECONDS); + } catch (TimeoutException ex) { + throw new ExecutionException(ex); + } + } +} + diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java index 0314cf683..5162047b2 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java @@ -34,6 +34,7 @@ package com.jme3.system.awt; import com.jme3.post.SceneProcessor; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; +import com.jme3.renderer.opengl.GLRenderer; import com.jme3.renderer.queue.RenderQueue; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image.Format; @@ -47,20 +48,19 @@ import java.awt.image.AffineTransformOp; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import java.nio.ByteBuffer; -import java.nio.IntBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; -public class AwtPanel extends Canvas implements SceneProcessor { +public class AwtPanel extends Canvas implements JmePanel, SceneProcessor { private boolean attachAsMain = false; private BufferedImage img; - private FrameBuffer fb; - private boolean srgb = false; - private ByteBuffer byteBuf; - private IntBuffer intBuf; +// private FrameBuffer fb; private RenderManager rm; private PaintMode paintMode; private ArrayList viewPorts = new ArrayList(); @@ -75,35 +75,47 @@ public class AwtPanel extends Canvas implements SceneProcessor { // Reshape vars private int newWidth = 1; private int newHeight = 1; - private AtomicBoolean reshapeNeeded = new AtomicBoolean(false); + private AtomicBoolean reshapeNeeded = new AtomicBoolean(true); private final Object lock = new Object(); - public AwtPanel(PaintMode paintMode){ - this(paintMode, false); - } + // Buffer pool and pending buffers + private int NUM_FRAMES = 3; + private final ArrayBlockingQueue> pendingFrames = new ArrayBlockingQueue>(NUM_FRAMES); + private final ArrayBlockingQueue bufferPool = new ArrayBlockingQueue(NUM_FRAMES); + private final ArrayList fbs = new ArrayList(NUM_FRAMES); + private int frameIndex = 0; + + + private final ComponentAdapter resizeListener = new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + onResize(e); + } + }; public AwtPanel(PaintMode paintMode, boolean srgb){ this.paintMode = paintMode; - this.srgb = srgb; + + invalidatePendingFrames(); + if (paintMode == PaintMode.Accelerated){ setIgnoreRepaint(true); } - addComponentListener(new ComponentAdapter(){ - @Override - public void componentResized(ComponentEvent e) { - synchronized (lock){ - int newWidth2 = Math.max(getWidth(), 1); - int newHeight2 = Math.max(getHeight(), 1); - if (newWidth != newWidth2 || newHeight != newHeight2){ - newWidth = newWidth2; - newHeight = newHeight2; - reshapeNeeded.set(true); - System.out.println("EDT: componentResized " + newWidth + ", " + newHeight); - } - } + addComponentListener(resizeListener); + } + + public void onResize(ComponentEvent e) { + synchronized (lock) { + int newWidth2 = Math.max(getWidth(), 1); + int newHeight2 = Math.max(getHeight(), 1); + if (newWidth != newWidth2 || newHeight != newHeight2) { + newWidth = newWidth2; + newHeight = newHeight2; + reshapeNeeded.set(true); + System.out.println("EDT: componentResized " + newWidth + ", " + newHeight); } - }); + } } @Override @@ -128,13 +140,13 @@ public class AwtPanel extends Canvas implements SceneProcessor { super.removeNotify(); } - @Override - public void paint(Graphics g){ - Graphics2D g2d = (Graphics2D) g; - synchronized (lock){ - g2d.drawImage(img, transformOp, 0, 0); - } - } +// @Override +// public void paint(Graphics g){ +// Graphics2D g2d = (Graphics2D) g; +// synchronized (lock){ +// g2d.drawImage(img, transformOp, 0, 0); +// } +// } public boolean checkVisibilityState(){ if (!hasNativePeer.get()){ @@ -157,24 +169,85 @@ public class AwtPanel extends Canvas implements SceneProcessor { return currentShowing; } - public void repaintInThread(){ - // Convert screenshot. - byteBuf.clear(); - rm.getRenderer().readFrameBuffer(fb, byteBuf); +// public void repaintInThread(){ +// // Convert screenshot. +// byteBuf.clear(); +// rm.getRenderer().readFrameBuffer(fb, byteBuf); +// +// synchronized (lock){ +// // All operations on img must be synchronized +// // as it is accessed from EDT. +// Screenshots.convertScreenShot2(intBuf, img); +// repaint(); +// } +// } + + public ByteBuffer acquireNextFrame() { + if (pendingFrames.isEmpty()) { + System.out.println("!!! No pending frames, returning null."); + return null; + } - synchronized (lock){ - // All operations on img must be synchronized - // as it is accessed from EDT. - Screenshots.convertScreenShot2(intBuf, img); - repaint(); + try { + ByteBuffer nextFrame = null; + +// while (!pendingFrames.isEmpty() && pendingFrames.peek().isDone()) { +// nextFrame = pendingFrames.take().get(); +// } +// +// if (nextFrame != null) { +// return nextFrame; +// } +// +// if (pendingFrames.remainingCapacity() == 0) { + // Force it to finish .. + return pendingFrames.take().get(); +// } + + // Some frames are pending, none are finished though. +// return null; + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } catch (ExecutionException ex) { + throw new RuntimeException(ex); } } - public void drawFrameInThread(){ - // Convert screenshot. - byteBuf.clear(); - rm.getRenderer().readFrameBuffer(fb, byteBuf); - Screenshots.convertScreenShot2(intBuf, img); + public void readNextFrame() { + if (bufferPool.isEmpty()) { + System.out.println("??? Too many pending frames!"); + return; // need to draw more frames .. + } + + try { + int size = fbs.get(frameIndex).getWidth() * fbs.get(frameIndex).getHeight() * 4; + ByteBuffer byteBuf = bufferPool.take(); + byteBuf = BufferUtils.ensureLargeEnough(byteBuf, size); + byteBuf.clear(); + + GLRenderer renderer = (GLRenderer) rm.getRenderer(); + Future future = renderer.readFrameBufferLater(fbs.get(frameIndex), byteBuf); + if (!pendingFrames.offer(future)) { + throw new AssertionError(); + } + + frameIndex ++; + if (frameIndex >= NUM_FRAMES) { + frameIndex = 0; + } + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } + + public void drawFrameInThread(ByteBuffer byteBuf){ + // Convert the frame into the image so it can be rendered. + Screenshots.convertScreenShot2(byteBuf.asIntBuffer(), img); + + // return the frame back to its rightful owner. + if (!bufferPool.offer(byteBuf)) { + throw new AssertionError(); + } synchronized (lock){ // All operations on strategy should be synchronized (?) @@ -235,44 +308,65 @@ public class AwtPanel extends Canvas implements SceneProcessor { if (this.rm == null){ // First time called in OGL thread this.rm = rm; - reshapeInThread(1, 1); +// reshapeInThread(1, 1); + } + } + + private void updateAccelerated() { + readNextFrame(); + ByteBuffer byteBuf = acquireNextFrame(); + if (byteBuf != null) { + drawFrameInThread(byteBuf); + } + } + + private void invalidatePendingFrames() { + // NOTE: all pending read requests are invalid! + for (Future pendingRequest : pendingFrames) { + pendingRequest.cancel(true); + } + pendingFrames.clear(); + bufferPool.clear(); + + // Populate buffer pool. + int cap = bufferPool.remainingCapacity(); + for (int i = 0; i < cap; i++) { + bufferPool.add(BufferUtils.createByteBuffer(1 * 1 * 4)); } } private void reshapeInThread(int width, int height) { - byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4); - intBuf = byteBuf.asIntBuffer(); - - if (fb != null) { + invalidatePendingFrames(); + + for (FrameBuffer fb : fbs) { fb.dispose(); - fb = null; } + fbs.clear(); - fb = new FrameBuffer(width, height, 1); - fb.setDepthBuffer(Format.Depth); - fb.setColorBuffer(Format.RGB8); - fb.setSrgb(srgb); - - if (attachAsMain){ - rm.getRenderer().setMainFrameBufferOverride(fb); + for (int i = 0; i < NUM_FRAMES; i++) { + FrameBuffer fb = new FrameBuffer(width, height, 1); + fb.setDepthBuffer(Format.Depth); + fb.setColorBuffer(Format.RGBA8); + fbs.add(fb); } + +// if (attachAsMain){ +// rm.getRenderer().setMainFrameBufferOverride(fb); +// } + synchronized (lock){ img = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); } -// synchronized (lock){ -// img = (BufferedImage) getGraphicsConfiguration().createCompatibleImage(width, height); -// } - AffineTransform tx = AffineTransform.getScaleInstance(1, -1); tx.translate(0, -img.getHeight()); transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); for (ViewPort vp : viewPorts){ - if (!attachAsMain){ - vp.setOutputFrameBuffer(fb); - } +// if (!attachAsMain){ +// vp.setOutputFrameBuffer(fb); +// } vp.getCamera().resize(width, height, true); // NOTE: Hack alert. This is done ONLY for custom framebuffers. @@ -283,8 +377,9 @@ public class AwtPanel extends Canvas implements SceneProcessor { } } + @Override public boolean isInitialized() { - return fb != null; + return rm != null; } public void preFrame(float tpf) { @@ -298,8 +393,21 @@ public class AwtPanel extends Canvas implements SceneProcessor { // For "PaintMode.OnDemand" only. repaintRequest.set(true); } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void onFrameBegin() { + if (attachAsMain && rm != null){ + rm.getRenderer().setMainFrameBufferOverride(fbs.get(frameIndex)); + } + } - void onFrameEnd() { + @Override + public void onFrameEnd() { if (reshapeNeeded.getAndSet(false)) { reshapeInThread(newWidth, newHeight); } else { @@ -309,26 +417,24 @@ public class AwtPanel extends Canvas implements SceneProcessor { switch (paintMode) { case Accelerated: - drawFrameInThread(); - break; - case Repaint: - repaintInThread(); - break; - case OnRequest: - if (repaintRequest.getAndSet(false)) { - repaintInThread(); - } + updateAccelerated(); break; +// case Repaint: +// repaintInThread(); +// break; +// case OnRequest: +// if (repaintRequest.getAndSet(false)) { +// repaintInThread(); +// } +// break; } } } public void postFrame(FrameBuffer out) { - if (!attachAsMain && out != fb){ - throw new IllegalStateException("Why did you change the output framebuffer?"); - } - - // onFrameEnd(); +// if (!attachAsMain && out != fb){ +// throw new IllegalStateException("Why did you change the output framebuffer?"); +// } } public void reshape(ViewPort vp, int w, int h) { diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java index 897632a00..cbd60a533 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java @@ -46,8 +46,8 @@ public class AwtPanelsContext implements JmeContext { protected JmeContext actualContext; protected AppSettings settings = new AppSettings(true); protected SystemListener listener; - protected ArrayList panels = new ArrayList(); - protected AwtPanel inputSource; + protected ArrayList panels = new ArrayList(); + protected JmePanel inputSource; protected AwtMouseInput mouseInput = new AwtMouseInput(); protected AwtKeyInput keyInput = new AwtKeyInput(); @@ -92,13 +92,13 @@ public class AwtPanelsContext implements JmeContext { } } - public void setInputSource(AwtPanel panel){ + public void setInputSource(JmePanel panel){ if (!panels.contains(panel)) throw new IllegalArgumentException(); inputSource = panel; - mouseInput.setInputSource(panel); - keyInput.setInputSource(panel); + mouseInput.setInputSource(panel.getComponent()); + keyInput.setInputSource(panel.getComponent()); } public Type getType() { @@ -148,14 +148,14 @@ public class AwtPanelsContext implements JmeContext { public AwtPanelsContext(){ } - public AwtPanel createPanel(PaintMode paintMode){ - AwtPanel panel = new AwtPanel(paintMode); + public JmePanel createPanel(PaintMode paintMode){ + JmePanel panel = new SwingPanel(paintMode, true); panels.add(panel); return panel; } - public AwtPanel createPanel(PaintMode paintMode, boolean srgb){ - AwtPanel panel = new AwtPanel(paintMode, srgb); + public JmePanel createPanel(PaintMode paintMode, boolean srgb){ + JmePanel panel = new SwingPanel(paintMode, srgb); panels.add(panel); return panel; } @@ -168,18 +168,18 @@ public class AwtPanelsContext implements JmeContext { // Check if throttle required boolean needThrottle = true; - for (AwtPanel panel : panels){ + for (JmePanel panel : panels){ if (panel.isActiveDrawing()){ needThrottle = false; break; } } - if (lastThrottleState != needThrottle){ + if (lastThrottleState != needThrottle) { lastThrottleState = needThrottle; - if (lastThrottleState){ + if (lastThrottleState) { System.out.println("OGL: Throttling update loop."); - }else{ + } else { System.out.println("OGL: Ceased throttling update loop."); } } @@ -191,9 +191,13 @@ public class AwtPanelsContext implements JmeContext { } } + for (JmePanel panel : panels){ + panel.onFrameBegin(); + } + listener.update(); - for (AwtPanel panel : panels){ + for (JmePanel panel : panels){ panel.onFrameEnd(); } } diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/JmePanel.java b/jme3-desktop/src/main/java/com/jme3/system/awt/JmePanel.java new file mode 100755 index 000000000..21bbbb753 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/JmePanel.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2015 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.system.awt; + +import com.jme3.renderer.ViewPort; +import java.awt.Component; + +public interface JmePanel { + + public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps); + + public boolean isActiveDrawing(); + + public void onFrameBegin(); + + public void onFrameEnd(); + + public Component getComponent(); +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/SwingPanel.java b/jme3-desktop/src/main/java/com/jme3/system/awt/SwingPanel.java new file mode 100755 index 000000000..8fbf7944e --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/SwingPanel.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2009-2015 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.system.awt; + +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.opengl.GLRenderer; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.util.BufferUtils; +import com.jme3.util.Screenshots; +import java.awt.AWTException; +import java.awt.BufferCapabilities; +import java.awt.Canvas; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.ImageCapabilities; +import java.awt.RenderingHints; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferStrategy; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JPanel; + +public class SwingPanel extends JPanel implements JmePanel, SceneProcessor { + + private boolean attachAsMain = false; + + private BufferedImage img; +// private FrameBuffer fb; + private RenderManager rm; + private PaintMode paintMode; + private ArrayList viewPorts = new ArrayList(); + + // Visibility/drawing vars + private AffineTransformOp transformOp; + private AtomicBoolean hasNativePeer = new AtomicBoolean(false); + private AtomicBoolean showing = new AtomicBoolean(false); + private AtomicBoolean repaintRequest = new AtomicBoolean(false); + + // Reshape vars + private int newWidth = 1; + private int newHeight = 1; + private AtomicBoolean reshapeNeeded = new AtomicBoolean(true); + private final Object lock = new Object(); + + // Buffer pool and pending buffers + private final int NUM_FRAMES = 2; + private final ArrayBlockingQueue> pendingFrames = new ArrayBlockingQueue>(NUM_FRAMES); + private final ArrayBlockingQueue bufferPool = new ArrayBlockingQueue(NUM_FRAMES); + private final ArrayList fbs = new ArrayList(NUM_FRAMES); + private int frameIndex = 0; + + private final ComponentAdapter resizeListener = new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + onResize(e); + } + }; + + public SwingPanel(PaintMode paintMode, boolean srgb){ + this.paintMode = paintMode; + invalidatePendingFrames(); + addComponentListener(resizeListener); + } + + public void onResize(ComponentEvent e) { + synchronized (lock) { + int newWidth2 = Math.max(getWidth(), 1); + int newHeight2 = Math.max(getHeight(), 1); + if (newWidth != newWidth2 || newHeight != newHeight2) { + newWidth = newWidth2; + newHeight = newHeight2; + reshapeNeeded.set(true); + System.out.println("EDT: componentResized " + newWidth + ", " + newHeight); + } + } + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void addNotify(){ + super.addNotify(); + + synchronized (lock){ + hasNativePeer.set(true); + System.out.println("EDT: addNotify"); + } + + requestFocusInWindow(); + } + + @Override + public void removeNotify(){ + synchronized (lock){ + hasNativePeer.set(false); + System.out.println("EDT: removeNotify"); + } + + super.removeNotify(); + } + + public boolean checkVisibilityState() { + if (!hasNativePeer.get()) { + return false; + } + + boolean currentShowing = isShowing(); + if (showing.getAndSet(currentShowing) != currentShowing) { + if (currentShowing) { + System.out.println("OGL: Enter showing state."); + } else { + System.out.println("OGL: Exit showing state."); + } + } + return currentShowing; + } + + @Override + public void paintComponent(Graphics g){ + Graphics2D g2d = (Graphics2D) g; + + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_SPEED); + + ByteBuffer byteBuf = null; + + synchronized (lock){ + if (pendingFrames.size() > NUM_FRAMES - 1) { + byteBuf = acquireNextFrame(); + } + + if (byteBuf != null) { + // Convert the frame into the image so it can be rendered. + Screenshots.convertScreenShot2(byteBuf.asIntBuffer(), img); + + try { + // return the frame back to its rightful owner. + bufferPool.put(byteBuf); + } catch (InterruptedException ex) { + Logger.getLogger(SwingPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + g2d.drawImage(img, transformOp, 0, 0); + } + + public ByteBuffer acquireNextFrame() { + if (pendingFrames.isEmpty()) { + System.out.println("!!! No pending frames, returning null."); + return null; + } + + try { + return pendingFrames.take().get(); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } catch (ExecutionException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Grabs an available buffer from the available frames pool, + * reads the OpenGL backbuffer into it, then adds it to the pending frames pool. + */ + public void readNextFrame() { + if (bufferPool.isEmpty()) { + System.out.println("??? Too many pending frames!"); + return; // need to draw more frames .. + } + + try { + int size = fbs.get(frameIndex).getWidth() * fbs.get(frameIndex).getHeight() * 4; + ByteBuffer byteBuf = bufferPool.take(); + byteBuf = BufferUtils.ensureLargeEnough(byteBuf, size); + byteBuf.clear(); + + GLRenderer renderer = (GLRenderer) rm.getRenderer(); +// Future future = renderer.readFrameBufferLater(fbs.get(frameIndex), byteBuf); +// if (!pendingFrames.offer(future)) { +// throw new AssertionError(); +// } + + frameIndex ++; + if (frameIndex >= NUM_FRAMES) { + frameIndex = 0; + } + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public boolean isActiveDrawing() { + return paintMode != PaintMode.OnRequest && showing.get(); + } + + public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps){ + if (viewPorts.size() > 0){ + for (ViewPort vp : viewPorts){ + vp.setOutputFrameBuffer(null); + } + viewPorts.get(viewPorts.size()-1).removeProcessor(this); + } + + viewPorts.addAll(Arrays.asList(vps)); + viewPorts.get(viewPorts.size()-1).addProcessor(this); + + this.attachAsMain = overrideMainFramebuffer; + } + + public void initialize(RenderManager rm, ViewPort vp) { + if (this.rm == null){ + // First time called in OGL thread + this.rm = rm; +// reshapeInThread(1, 1); + } + } + + private void invalidatePendingFrames() { + // NOTE: all pending read requests are invalid! + for (Future pendingRequest : pendingFrames) { + pendingRequest.cancel(true); + } + pendingFrames.clear(); + bufferPool.clear(); + + // Populate buffer pool. + int cap = bufferPool.remainingCapacity(); + for (int i = 0; i < cap; i++) { + bufferPool.add(BufferUtils.createByteBuffer(1 * 1 * 4)); + } + } + + private void reshapeInThread(int width, int height) { + invalidatePendingFrames(); + + for (FrameBuffer fb : fbs) { + fb.dispose(); + } + + fbs.clear(); + + for (int i = 0; i < NUM_FRAMES; i++) { + FrameBuffer fb = new FrameBuffer(width, height, 1); + fb.setDepthBuffer(Image.Format.Depth); + fb.setColorBuffer(Image.Format.RGBA8); + fbs.add(fb); + } + + synchronized (lock){ + img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + AffineTransform tx = AffineTransform.getScaleInstance(1, -1); + tx.translate(0, -img.getHeight()); + transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + } + + if (attachAsMain) { + rm.notifyReshape(width, height); + } else { + for (ViewPort vp : viewPorts){ + vp.getCamera().resize(width, height, true); + + // NOTE: Hack alert. This is done ONLY for custom framebuffers. + // Main framebuffer should use RenderManager.notifyReshape(). + for (SceneProcessor sp : vp.getProcessors()){ + sp.reshape(vp, width, height); + } + } + } + } + + @Override + public boolean isInitialized() { + return rm != null; + } + + @Override + public void preFrame(float tpf) { + } + + @Override + public void postQueue(RenderQueue rq) { + } + + @Override + public void invalidate(){ + // For "PaintMode.OnDemand" only. + repaintRequest.set(true); + } + + @Override + public void onFrameBegin() { + if (attachAsMain && rm != null){ + rm.getRenderer().setMainFrameBufferOverride(fbs.get(frameIndex)); + } + } + + @Override + public void onFrameEnd() { + if (reshapeNeeded.getAndSet(false)) { + reshapeInThread(newWidth, newHeight); + } else { + if (!checkVisibilityState()) { + return; + } + + switch (paintMode) { + case Accelerated: + case Repaint: + readNextFrame(); + repaint(); + break; + case OnRequest: + if (repaintRequest.getAndSet(false)) { + readNextFrame(); + repaint(); + } + break; + } + } + } + + public void postFrame(FrameBuffer out) { + } + + public void reshape(ViewPort vp, int w, int h) { + } + + public void cleanup() { + } +} From f0b63e7910ee6f54c58cbad2b986101dc0067fef Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Fri, 21 Aug 2015 21:34:43 -0400 Subject: [PATCH 4/4] GLRenderer: the actual async FB read changes --- .../com/jme3/renderer/opengl/GLRenderer.java | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 83644bcaa..41cc430e8 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -62,6 +62,7 @@ import java.util.EnumMap; import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -98,7 +99,8 @@ public class GLRenderer implements Renderer { private final GLExt glext; private final GLFbo glfbo; private final TextureUtil texUtil; - + private final AsyncFrameReader frameReader; + public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) { this.gl = gl; this.gl2 = gl instanceof GL2 ? (GL2)gl : null; @@ -107,6 +109,7 @@ public class GLRenderer implements Renderer { this.glfbo = glfbo; this.glext = glext; this.texUtil = new TextureUtil(gl, gl2, glext); + this.frameReader = new AsyncFrameReader(this, gl, glext, context); } @Override @@ -861,6 +864,7 @@ public class GLRenderer implements Renderer { public void postFrame() { objManager.deleteUnused(this); + frameReader.updateReadRequests(); gl.resetStats(); } @@ -1647,11 +1651,11 @@ public class GLRenderer implements Renderer { } } - public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { - readFrameBufferWithGLFormat(fb, byteBuf, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); + public Future readFrameBufferLater(FrameBuffer fb, ByteBuffer byteBuf) { + return frameReader.readFrameBufferLater(fb, byteBuf); } - - private void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType) { + + void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType, int pboId) { if (fb != null) { RenderBuffer rb = fb.getColorBuffer(); if (rb == null) { @@ -1670,12 +1674,30 @@ public class GLRenderer implements Renderer { setFrameBuffer(null); } - gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf); + if (context.boundPixelPackPBO != pboId) { + gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, pboId); + context.boundPixelPackPBO = pboId; + } + + if (byteBuf == null) { + gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, 0); + } else { + gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf); + } + + if (context.boundPixelPackPBO != 0) { + gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, 0); + context.boundPixelPackPBO = 0; + } } public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { GLImageFormat glFormat = texUtil.getImageFormatWithError(format, false); - readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType); + readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType, 0); + } + + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + readFrameBufferWithFormat(fb, byteBuf, Image.Format.RGBA8); } private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { @@ -2284,6 +2306,7 @@ public class GLRenderer implements Renderer { } context.attribIndexList.copyNewToOld(); } + private int updateAttributeLocation(Shader shader, VertexBuffer.Type attribType) { Attribute attrib = shader.getAttribute(attribType); @@ -2550,8 +2573,8 @@ public class GLRenderer implements Renderer { } /*********************************************************************\ - |* Render Calls *| - \*********************************************************************/ + |* Render Calls *| + \*********************************************************************/ public int convertElementMode(Mesh.Mode mode) { switch (mode) { case Points: