com.jme3.scene.VertexBuffer Maven / Gradle / Ivy
Show all versions of jme3-core Show documentation
/*
* Copyright (c) 2009-2021 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.scene;
import com.jme3.export.*;
import com.jme3.math.FastMath;
import com.jme3.renderer.Renderer;
import com.jme3.util.BufferUtils;
import com.jme3.util.NativeObject;
import java.io.IOException;
import java.nio.*;
/**
* A VertexBuffer
contains a particular type of geometry
* data used by {@link Mesh}es. Every VertexBuffer set on a Mesh
* is sent as an attribute to the vertex shader to be processed.
*
* Several terms are used throughout the javadoc for this class, explanation:
*
* - Element - A single element is the largest individual object
* inside a VertexBuffer. E.g. if the VertexBuffer is used to store 3D position
* data, then an element will be a single 3D vector.
* - Component - A component represents the parts inside an element.
* For a 3D vector, a single component is one of the dimensions, X, Y or Z.
*
*/
public class VertexBuffer extends NativeObject implements Savable, Cloneable {
/**
* Type of buffer. Specifies the actual attribute it defines.
*/
public static enum Type {
/**
* Position of the vertex (3 floats)
*/
Position,
/**
* The size of the point when using point buffers (float).
*/
Size,
/**
* Normal vector, normalized (3 floats).
*/
Normal,
/**
* Texture coordinate (2 float)
*/
TexCoord,
/**
* Color and Alpha (4 floats)
*/
Color,
/**
* Tangent vector, normalized (4 floats) (x,y,z,w). The w component is
* called the binormal parity, is not normalized, and is either 1f or
* -1f. It's used to compute the direction on the binormal vector on the
* GPU at render time.
*/
Tangent,
/**
* Binormal vector, normalized (3 floats, optional)
*/
Binormal,
/**
* Specifies the source data for various vertex buffers
* when interleaving is used. By default, the format is
* byte.
*/
InterleavedData,
/**
* Do not use.
*/
@Deprecated
Reserved0,
/**
* Specifies the index buffer, must contain integer data
* (ubyte, ushort, or uint).
*/
Index,
/**
* Initial vertex position, used with animation.
* Should have the same format and size as {@link Type#Position}.
* If used with software skinning, the usage should be
* {@link Usage#CpuOnly}, and the buffer should be allocated
* on the heap.
*/
BindPosePosition,
/**
* Initial vertex normals, used with animation.
* Should have the same format and size as {@link Type#Normal}.
* If used with software skinning, the usage should be
* {@link Usage#CpuOnly}, and the buffer should be allocated
* on the heap.
*/
BindPoseNormal,
/**
* Bone weights, used with animation (4 floats).
* Only used for software skinning, the usage should be
* {@link Usage#CpuOnly}, and the buffer should be allocated
* on the heap.
*/
BoneWeight,
/**
* Bone indices, used with animation (4 ubytes).
* Only used for software skinning, the usage should be
* {@link Usage#CpuOnly}, and the buffer should be allocated
* on the heap as a ubytes buffer.
*/
BoneIndex,
/**
* Texture coordinate #2
*/
TexCoord2,
/**
* Texture coordinate #3
*/
TexCoord3,
/**
* Texture coordinate #4
*/
TexCoord4,
/**
* Texture coordinate #5
*/
TexCoord5,
/**
* Texture coordinate #6
*/
TexCoord6,
/**
* Texture coordinate #7
*/
TexCoord7,
/**
* Texture coordinate #8
*/
TexCoord8,
/**
* Initial vertex tangents, used with animation.
* Should have the same format and size as {@link Type#Tangent}.
* If used with software skinning, the usage should be
* {@link Usage#CpuOnly}, and the buffer should be allocated
* on the heap.
*/
BindPoseTangent,
/**
* Bone weights, used with animation (4 floats).
* for Hardware Skinning only
*/
HWBoneWeight,
/**
* Bone indices, used with animation (4 ubytes).
* for Hardware Skinning only
* either an int or float buffer due to shader attribute types restrictions.
*/
HWBoneIndex,
/**
* Information about this instance.
*
* Format should be {@link Format#Float} and number of components
* should be 16.
*/
InstanceData,
/**
* Morph animations targets.
* Supports up tp 14 morph target buffers at the same time
* Limited due to the limited number of attributes you can bind to a vertex shader usually 16
*
* MorphTarget buffers are either POSITION, NORMAL or TANGENT buffers.
* So we can support up to
* 14 simultaneous POSITION targets
* 7 simultaneous POSITION and NORMAL targets
* 4 simultaneous POSITION, NORMAL and TANGENT targets.
*
* Note that the MorphControl will find how many buffers
* can be supported for each mesh/material combination.
* Note that all buffers have 3 components (Vector3f)
* even the Tangent buffer that
* does not contain the w (handedness) component
* that will not be interpolated for morph animation.
*
* Note that those buffers contain the difference between
* the base buffer (POSITION, NORMAL or TANGENT) and the target value
* So that you can interpolate with a MADD operation in the vertex shader
* position = weight * diffPosition + basePosition;
*/
MorphTarget0,
MorphTarget1,
MorphTarget2,
MorphTarget3,
MorphTarget4,
MorphTarget5,
MorphTarget6,
MorphTarget7,
MorphTarget8,
MorphTarget9,
MorphTarget10,
MorphTarget11,
MorphTarget12,
MorphTarget13,
}
/**
* The usage of the VertexBuffer, specifies how often the buffer
* is used. This can determine if a vertex buffer is placed in VRAM
* or held in video memory, but no guarantees are made- it's only a hint.
*/
public static enum Usage {
/**
* Mesh data is sent once and very rarely updated.
*/
Static,
/**
* Mesh data is updated occasionally (once per frame or less).
*/
Dynamic,
/**
* Mesh data is updated every frame.
*/
Stream,
/**
* Mesh data is not sent to GPU at all. It is only
* used by the CPU.
*/
CpuOnly
}
/**
* Specifies format of the data stored in the buffer.
* This should directly correspond to the buffer's class, for example,
* an {@link Format#UnsignedShort} formatted buffer should use the
* class {@link ShortBuffer} (e.g. the closest resembling type).
* For the {@link Format#Half} type, {@link ByteBuffer}s should
* be used.
*/
public static enum Format {
/**
* Half precision floating point. 2 bytes, signed.
*/
Half(2),
/**
* Single precision floating point. 4 bytes, signed
*/
Float(4),
/**
* Double precision floating point. 8 bytes, signed. May not be
* supported by all GPUs.
*/
Double(8),
/**
* 1 byte integer, signed.
*/
Byte(1),
/**
* 1 byte integer, unsigned.
*/
UnsignedByte(1),
/**
* 2 byte integer, signed.
*/
Short(2),
/**
* 2 byte integer, unsigned.
*/
UnsignedShort(2),
/**
* 4 byte integer, signed.
*/
Int(4),
/**
* 4 byte integer, unsigned.
*/
UnsignedInt(4);
private int componentSize = 0;
Format(int componentSize) {
this.componentSize = componentSize;
}
/**
* Returns the size in bytes of this data type.
*
* @return Size in bytes of this data type.
*/
public int getComponentSize() {
return componentSize;
}
}
protected int offset = 0;
protected int lastLimit = 0;
protected int stride = 0;
protected int components = 0;
/**
* derived from components * format.getComponentSize()
*/
protected transient int componentsLength = 0;
protected Buffer data = null;
protected Usage usage;
protected Type bufType;
protected Format format;
protected boolean normalized = false;
protected int instanceSpan = 0;
protected transient boolean dataSizeChanged = false;
/**
* Creates an empty, uninitialized buffer.
* Must call setupData() to initialize.
*
* @param type the type of VertexBuffer, such as Position or Binormal
*/
public VertexBuffer(Type type) {
super();
this.bufType = type;
}
/**
* Serialization only. Do not use.
*/
protected VertexBuffer() {
super();
}
protected VertexBuffer(int id) {
super(id);
}
public boolean invariant() {
// Does the VB hold any data?
if (data == null) {
throw new AssertionError();
}
// Position must be 0.
if (data.position() != 0) {
throw new AssertionError();
}
// Is the size of the VB == 0?
if (data.limit() == 0) {
throw new AssertionError();
}
// Does offset exceed buffer limit or negative?
if (offset > data.limit() || offset < 0) {
throw new AssertionError();
}
// Are components between 1 and 4?
// Are components between 1 and 4 and not InstanceData?
if (bufType != Type.InstanceData) {
if (components < 1 || components > 4) {
throw new AssertionError();
}
}
// Does usage comply with buffer directness?
//if (usage == Usage.CpuOnly && data.isDirect()) {
// throw new AssertionError();
/*} else*/ if (usage != Usage.CpuOnly && !data.isDirect()) {
throw new AssertionError();
}
// Double/Char/Long buffers are not supported for VertexBuffers.
// For the rest, ensure they comply with the "Format" value.
if (data instanceof DoubleBuffer) {
throw new AssertionError();
} else if (data instanceof CharBuffer) {
throw new AssertionError();
} else if (data instanceof LongBuffer) {
throw new AssertionError();
} else if (data instanceof FloatBuffer && format != Format.Float) {
throw new AssertionError();
} else if (data instanceof IntBuffer && format != Format.Int && format != Format.UnsignedInt) {
throw new AssertionError();
} else if (data instanceof ShortBuffer && format != Format.Short && format != Format.UnsignedShort) {
throw new AssertionError();
} else if (data instanceof ByteBuffer && format != Format.Byte && format != Format.UnsignedByte) {
throw new AssertionError();
}
return true;
}
/**
* @return The offset after which the data is sent to the GPU.
*
* @see #setOffset(int)
*/
public int getOffset() {
return offset;
}
/**
* @param offset Specify the offset (in bytes) from the start of the buffer
* after which the data is sent to the GPU.
*/
public void setOffset(int offset) {
this.offset = offset;
}
/**
* @return The stride (in bytes) for the data.
*
* @see #setStride(int)
*/
public int getStride() {
return stride;
}
/**
* Set the stride (in bytes) for the data.
*
* If the data is packed in the buffer, then stride is 0, if there's other
* data that is between the current component and the next component in the
* buffer, then this specifies the size in bytes of that additional data.
*
* @param stride the stride (in bytes) for the data
*/
public void setStride(int stride) {
this.stride = stride;
}
/**
* Returns the raw internal data buffer used by this VertexBuffer.
* This buffer is not safe to call from multiple threads since buffers
* have their own internal position state that cannot be shared.
* Call getData().duplicate(), getData().asReadOnlyBuffer(), or
* the more convenient getDataReadOnly() if the buffer may be accessed
* from multiple threads.
*
* @return A native buffer, in the specified {@link Format format}.
*/
public Buffer getData() {
return data;
}
/**
* Returns a safe read-only version of this VertexBuffer's data. The
* contents of the buffer will reflect whatever changes are made on
* other threads (eventually) but these should not be used in that way.
* This method provides a read-only buffer that is safe to _read_ from
* a separate thread since it has its own book-keeping state (position, limit, etc.)
*
* @return A rewound native buffer in the specified {@link Format format}
* that is safe to read from a separate thread from other readers.
*/
public Buffer getDataReadOnly() {
if (data == null) {
return null;
}
// Create a read-only duplicate(). Note: this does not copy
// the underlying memory, it just creates a new read-only wrapper
// with its own buffer position state.
// Unfortunately, this is not 100% straight forward since Buffer
// does not have an asReadOnlyBuffer() method.
Buffer result;
if (data instanceof ByteBuffer) {
result = ((ByteBuffer) data).asReadOnlyBuffer();
} else if (data instanceof FloatBuffer) {
result = ((FloatBuffer) data).asReadOnlyBuffer();
} else if (data instanceof ShortBuffer) {
result = ((ShortBuffer) data).asReadOnlyBuffer();
} else if (data instanceof IntBuffer) {
result = ((IntBuffer) data).asReadOnlyBuffer();
} else {
throw new UnsupportedOperationException("Cannot get read-only view of buffer type:" + data);
}
// Make sure the caller gets a consistent view since we may
// have grabbed this buffer while another thread was reading
// the raw data.
result.rewind();
return result;
}
/**
* @return The usage of this buffer. See {@link Usage} for more
* information.
*/
public Usage getUsage() {
return usage;
}
/**
* @param usage The usage of this buffer. See {@link Usage} for more
* information.
*/
public void setUsage(Usage usage) {
// if (id != -1)
// throw new UnsupportedOperationException("Data has already been sent. Cannot set usage.");
this.usage = usage;
this.setUpdateNeeded();
}
/**
* @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
* a floating-point value for the shader.
* E.g. if the {@link Format} is {@link Format#UnsignedInt}, then
* the components will be converted to the range 0.0 - 1.0 by dividing
* every integer by 2^32.
*/
public void setNormalized(boolean normalized) {
this.normalized = normalized;
}
/**
* @return True if integer components should be converted to the range 0-1.
* @see VertexBuffer#setNormalized(boolean)
*/
public boolean isNormalized() {
return normalized;
}
/**
* Sets the instanceSpan to 1 or 0 depending on
* the value of instanced and the existing value of
* instanceSpan.
*
* @param instanced true for instanced, false for not instanced
*/
public void setInstanced(boolean instanced) {
if (instanced && instanceSpan == 0) {
instanceSpan = 1;
} else if (!instanced) {
instanceSpan = 0;
}
}
/**
* @return true if buffer contains per-instance data, otherwise false
*/
public boolean isInstanced() {
return instanceSpan > 0;
}
/**
* Sets how this vertex buffer matches with rendered instances
* where 0 means no instancing at all, ie: all elements are
* per vertex. If set to 1 then each element goes with one
* instance. If set to 2 then each element goes with two
* instances and so on.
*
* @param i the desired number of instances per element
*/
public void setInstanceSpan(int i) {
this.instanceSpan = i;
}
public int getInstanceSpan() {
return instanceSpan;
}
/**
* @return The type of information that this buffer has.
*/
public Type getBufferType() {
return bufType;
}
/**
* @return The {@link Format format}, or data type of the data.
*/
public Format getFormat() {
return format;
}
/**
* @return The number of components of the given {@link Format format} per
* element.
*/
public int getNumComponents() {
return components;
}
/**
* @return The total number of data elements in the data buffer.
*/
public int getNumElements() {
if (data == null) {
return 0;
}
int elements = data.limit() / components;
if (format == Format.Half) {
elements /= 2;
}
return elements;
}
/**
* Returns the number of 'instances' in this VertexBuffer. This
* is dependent on the current instanceSpan. When instanceSpan
* is 0 then 'instances' is 1. Otherwise, instances is elements *
* instanceSpan. It is possible to render a mesh with more instances
* but the instance data begins to repeat.
*
* @return the number of instances
*/
public int getBaseInstanceCount() {
if (instanceSpan == 0) {
return 1;
}
return getNumElements() * instanceSpan;
}
/**
* Called to initialize the data in the VertexBuffer
. Must only
* be called once.
*
* @param usage The usage for the data, or how often will the data
* be updated per frame. See the {@link Usage} enum.
* @param components The number of components per element.
* @param format The {@link Format format}, or data-type of a single
* component.
* @param data A native buffer, the format of which matches the {@link Format}
* argument.
*/
public void setupData(Usage usage, int components, Format format, Buffer data) {
if (id != -1) {
throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again.");
}
if (usage == null || format == null || data == null) {
throw new IllegalArgumentException("None of the arguments can be null");
}
if (data.isReadOnly()) {
throw new IllegalArgumentException("VertexBuffer data cannot be read-only.");
}
if (bufType != Type.InstanceData) {
if (components < 1 || components > 4) {
throw new IllegalArgumentException("components must be between 1 and 4");
}
}
this.data = data;
this.components = components;
this.usage = usage;
this.format = format;
this.componentsLength = components * format.getComponentSize();
this.lastLimit = data.limit();
setUpdateNeeded();
}
/**
* Called to update the data in the buffer with new data. Can only
* be called after {@link VertexBuffer#setupData(
* com.jme3.scene.VertexBuffer.Usage, int, com.jme3.scene.VertexBuffer.Format, java.nio.Buffer) }
* has been called. Note that it is fine to call this method on the
* data already set, e.g. vb.updateData(vb.getData()), this will just
* set the proper update flag indicating the data should be sent to the GPU
* again.
*
* It is allowed to specify a buffer with different capacity than the
* originally set buffer, HOWEVER, if you do so, you must
* call Mesh.updateCounts() otherwise bizarre errors can occur.
*
* @param data The data buffer to set
*/
public void updateData(Buffer data) {
if (id != -1) {
// request to update data is okay
}
// Check if the data buffer is read-only which is a sign
// of a bug on the part of the caller
if (data != null && data.isReadOnly()) {
throw new IllegalArgumentException("VertexBuffer data cannot be read-only.");
}
// will force renderer to call glBufferData again
if (data != null && (this.data.getClass() != data.getClass() || data.limit() != lastLimit)) {
dataSizeChanged = true;
lastLimit = data.limit();
}
this.data = data;
setUpdateNeeded();
}
/**
* Returns true if the data size of the VertexBuffer has changed.
* Internal use only.
*
* @return true if the data size has changed
*/
public boolean hasDataSizeChanged() {
return dataSizeChanged;
}
@Override
public void clearUpdateNeeded() {
super.clearUpdateNeeded();
dataSizeChanged = false;
}
/**
* Converts single floating-point data to {@link Format#Half half} floating-point data.
*/
public void convertToHalf() {
if (id != -1) {
throw new UnsupportedOperationException("Data has already been sent.");
}
if (format != Format.Float) {
throw new IllegalStateException("Format must be float!");
}
int numElements = data.limit() / components;
format = Format.Half;
this.componentsLength = components * format.getComponentSize();
ByteBuffer halfData = BufferUtils.createByteBuffer(componentsLength * numElements);
halfData.rewind();
FloatBuffer floatData = (FloatBuffer) data;
floatData.rewind();
for (int i = 0; i < floatData.limit(); i++) {
float f = floatData.get(i);
short half = FastMath.convertFloatToHalf(f);
halfData.putShort(half);
}
this.data = halfData;
setUpdateNeeded();
dataSizeChanged = true;
}
/**
* Reduces the capacity of the buffer to the given amount
* of elements, any elements at the end of the buffer are truncated
* as necessary.
*
* @param numElements The number of elements to reduce to.
*/
public void compact(int numElements) {
int total = components * numElements;
data.clear();
switch (format) {
case Byte:
case UnsignedByte:
case Half:
ByteBuffer bbuf = (ByteBuffer) data;
bbuf.limit(total);
ByteBuffer bnewBuf = BufferUtils.createByteBuffer(total);
bnewBuf.put(bbuf);
data = bnewBuf;
break;
case Short:
case UnsignedShort:
ShortBuffer sbuf = (ShortBuffer) data;
sbuf.limit(total);
ShortBuffer snewBuf = BufferUtils.createShortBuffer(total);
snewBuf.put(sbuf);
data = snewBuf;
break;
case Int:
case UnsignedInt:
IntBuffer ibuf = (IntBuffer) data;
ibuf.limit(total);
IntBuffer inewBuf = BufferUtils.createIntBuffer(total);
inewBuf.put(ibuf);
data = inewBuf;
break;
case Float:
FloatBuffer fbuf = (FloatBuffer) data;
fbuf.limit(total);
FloatBuffer fnewBuf = BufferUtils.createFloatBuffer(total);
fnewBuf.put(fbuf);
data = fnewBuf;
break;
default:
throw new UnsupportedOperationException("Unrecognized buffer format: " + format);
}
data.clear();
setUpdateNeeded();
dataSizeChanged = true;
}
/**
* Modify a component inside an element.
* The val
parameter must be in the buffer's format:
* {@link Format}.
*
* @param elementIndex The element index to modify
* @param componentIndex The component index to modify
* @param val The value to set, either byte, short, int or float depending
* on the {@link Format}.
*/
public void setElementComponent(int elementIndex, int componentIndex, Object val) {
int inPos = elementIndex * components;
int elementPos = componentIndex;
if (format == Format.Half) {
inPos *= 2;
elementPos *= 2;
}
data.clear();
switch (format) {
case Byte:
case UnsignedByte:
case Half:
ByteBuffer bin = (ByteBuffer) data;
bin.put(inPos + elementPos, (Byte) val);
break;
case Short:
case UnsignedShort:
ShortBuffer sin = (ShortBuffer) data;
sin.put(inPos + elementPos, (Short) val);
break;
case Int:
case UnsignedInt:
IntBuffer iin = (IntBuffer) data;
iin.put(inPos + elementPos, (Integer) val);
break;
case Float:
FloatBuffer fin = (FloatBuffer) data;
fin.put(inPos + elementPos, (Float) val);
break;
default:
throw new UnsupportedOperationException("Unrecognized buffer format: " + format);
}
}
/**
* Get the component inside an element.
*
* @param elementIndex The element index
* @param componentIndex The component index
* @return The component, as one of the primitive types, byte, short,
* int or float.
*/
public Object getElementComponent(int elementIndex, int componentIndex) {
int inPos = elementIndex * components;
int elementPos = componentIndex;
if (format == Format.Half) {
inPos *= 2;
elementPos *= 2;
}
Buffer srcData = getDataReadOnly();
switch (format) {
case Byte:
case UnsignedByte:
case Half:
ByteBuffer bin = (ByteBuffer) srcData;
return bin.get(inPos + elementPos);
case Short:
case UnsignedShort:
ShortBuffer sin = (ShortBuffer) srcData;
return sin.get(inPos + elementPos);
case Int:
case UnsignedInt:
IntBuffer iin = (IntBuffer) srcData;
return iin.get(inPos + elementPos);
case Float:
FloatBuffer fin = (FloatBuffer) srcData;
return fin.get(inPos + elementPos);
default:
throw new UnsupportedOperationException("Unrecognized buffer format: " + format);
}
}
/**
* Copies a single element of data from this VertexBuffer
* to the given output VertexBuffer.
*
* @param inIndex The input element index
* @param outVb The buffer to copy to
* @param outIndex The output element index
*
* @throws IllegalArgumentException If the formats of the buffers do not
* match.
*/
public void copyElement(int inIndex, VertexBuffer outVb, int outIndex) {
copyElements(inIndex, outVb, outIndex, 1);
}
/**
* Copies a sequence of elements of data from this VertexBuffer
* to the given output VertexBuffer.
*
* @param inIndex The input element index
* @param outVb The buffer to copy to
* @param outIndex The output element index
* @param len The number of elements to copy
*
* @throws IllegalArgumentException If the formats of the buffers do not
* match.
*/
public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len) {
if (outVb.format != format || outVb.components != components) {
throw new IllegalArgumentException("Buffer format mismatch. Cannot copy");
}
int inPos = inIndex * components;
int outPos = outIndex * components;
int elementSz = components;
if (format == Format.Half) {
// because half is stored as ByteBuffer, but it's 2 bytes long
inPos *= 2;
outPos *= 2;
elementSz *= 2;
}
// Make sure to grab a read-only copy in case some other
// thread is also accessing the buffer and messing with its
// position()
Buffer srcData = getDataReadOnly();
outVb.data.clear();
switch (format) {
case Byte:
case UnsignedByte:
case Half:
ByteBuffer bin = (ByteBuffer) srcData;
ByteBuffer bout = (ByteBuffer) outVb.data;
bin.position(inPos).limit(inPos + elementSz * len);
bout.position(outPos).limit(outPos + elementSz * len);
bout.put(bin);
break;
case Short:
case UnsignedShort:
ShortBuffer sin = (ShortBuffer) srcData;
ShortBuffer sout = (ShortBuffer) outVb.data;
sin.position(inPos).limit(inPos + elementSz * len);
sout.position(outPos).limit(outPos + elementSz * len);
sout.put(sin);
break;
case Int:
case UnsignedInt:
IntBuffer iin = (IntBuffer) srcData;
IntBuffer iout = (IntBuffer) outVb.data;
iin.position(inPos).limit(inPos + elementSz * len);
iout.position(outPos).limit(outPos + elementSz * len);
iout.put(iin);
break;
case Float:
FloatBuffer fin = (FloatBuffer) srcData;
FloatBuffer fout = (FloatBuffer) outVb.data;
fin.position(inPos).limit(inPos + elementSz * len);
fout.position(outPos).limit(outPos + elementSz * len);
fout.put(fin);
break;
default:
throw new UnsupportedOperationException("Unrecognized buffer format: " + format);
}
// Clear the output buffer to rewind it and reset its
// limit from where we shortened it above.
outVb.data.clear();
}
/**
* Creates a {@link Buffer} that satisfies the given type and size requirements
* 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 desired format of components, such as Float or Half
* @param components the number of components per element (≥1, ≤4)
* @param numElements the desired capacity (number of elements)
* @return a new Buffer
*/
public static Buffer createBuffer(Format format, int components, int numElements) {
if (components < 1 || components > 4) {
throw new IllegalArgumentException("Num components must be between 1 and 4");
}
int total = numElements * components;
switch (format) {
case Byte:
case UnsignedByte:
return BufferUtils.createByteBuffer(total);
case Half:
return BufferUtils.createByteBuffer(total * 2);
case Short:
case UnsignedShort:
return BufferUtils.createShortBuffer(total);
case Int:
case UnsignedInt:
return BufferUtils.createIntBuffer(total);
case Float:
return BufferUtils.createFloatBuffer(total);
case Double:
return BufferUtils.createDoubleBuffer(total);
default:
throw new UnsupportedOperationException("Unrecognized buffer format: " + format);
}
}
/**
* Creates a deep clone of the {@link VertexBuffer}.
*
* @return Deep clone of this buffer
*/
@Override
public VertexBuffer clone() {
// NOTE: Superclass GLObject automatically creates shallow clone
// e.g. re-use ID.
VertexBuffer vb = (VertexBuffer) super.clone();
vb.handleRef = new Object();
vb.id = -1;
if (data != null) {
// Make sure to pass a read-only buffer to clone so that
// the position information doesn't get clobbered by another
// reading thread during cloning (and vice versa) since this is
// a purely read-only operation.
vb.updateData(BufferUtils.clone(getDataReadOnly()));
}
return vb;
}
/**
* Creates a deep clone of this VertexBuffer but overrides the
* {@link Type}.
*
* @param overrideType The type of the cloned VertexBuffer
* @return A deep clone of the buffer
*/
public VertexBuffer clone(Type overrideType) {
VertexBuffer vb = new VertexBuffer(overrideType);
vb.components = components;
vb.componentsLength = componentsLength;
// Make sure to pass a read-only buffer to clone so that
// the position information doesn't get clobbered by another
// reading thread during cloning (and vice versa) since this is
// a purely read-only operation.
vb.data = BufferUtils.clone(getDataReadOnly());
vb.format = format;
vb.handleRef = new Object();
vb.id = -1;
vb.normalized = normalized;
vb.instanceSpan = instanceSpan;
vb.offset = offset;
vb.stride = stride;
vb.updateNeeded = true;
vb.usage = usage;
return vb;
}
@Override
public String toString() {
String dataTxt = null;
if (data != null) {
dataTxt = ", elements=" + data.limit();
}
return getClass().getSimpleName() + "[fmt=" + format.name()
+ ", type=" + bufType.name()
+ ", usage=" + usage.name()
+ dataTxt + "]";
}
@Override
public void resetObject() {
// assert this.id != -1;
this.id = -1;
setUpdateNeeded();
}
@Override
public void deleteObject(Object rendererObject) {
((Renderer) rendererObject).deleteBuffer(this);
}
@Override
protected void deleteNativeBuffers() {
if (data != null) {
BufferUtils.destroyDirectBuffer(data);
}
}
@Override
public NativeObject createDestructableClone() {
return new VertexBuffer(id);
}
@Override
public long getUniqueId() {
return ((long) OBJTYPE_VERTEXBUFFER << 32) | ((long) id);
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(components, "components", 0);
oc.write(usage, "usage", Usage.Dynamic);
oc.write(bufType, "buffer_type", null);
oc.write(format, "format", Format.Float);
oc.write(normalized, "normalized", false);
oc.write(offset, "offset", 0);
oc.write(stride, "stride", 0);
oc.write(instanceSpan, "instanceSpan", 0);
String dataName = "data" + format.name();
Buffer roData = getDataReadOnly();
switch (format) {
case Float:
oc.write((FloatBuffer) roData, dataName, null);
break;
case Short:
case UnsignedShort:
oc.write((ShortBuffer) roData, dataName, null);
break;
case UnsignedByte:
case Byte:
case Half:
oc.write((ByteBuffer) roData, dataName, null);
break;
case Int:
case UnsignedInt:
oc.write((IntBuffer) roData, dataName, null);
break;
default:
throw new IOException("Unsupported export buffer format: " + format);
}
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
components = ic.readInt("components", 0);
usage = ic.readEnum("usage", Usage.class, Usage.Dynamic);
bufType = ic.readEnum("buffer_type", Type.class, null);
format = ic.readEnum("format", Format.class, Format.Float);
normalized = ic.readBoolean("normalized", false);
offset = ic.readInt("offset", 0);
stride = ic.readInt("stride", 0);
instanceSpan = ic.readInt("instanceSpan", 0);
componentsLength = components * format.getComponentSize();
String dataName = "data" + format.name();
switch (format) {
case Float:
data = ic.readFloatBuffer(dataName, null);
break;
case Short:
case UnsignedShort:
data = ic.readShortBuffer(dataName, null);
break;
case UnsignedByte:
case Byte:
case Half:
data = ic.readByteBuffer(dataName, null);
break;
case Int:
case UnsignedInt:
data = ic.readIntBuffer(dataName, null);
break;
default:
throw new IOException("Unsupported import buffer format: " + format);
}
}
}