1/*******************************************************************************
2 * Copyright 2011 See AUTHORS file.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 ******************************************************************************/
16
17package com.badlogic.gdx.graphics.glutils;
18
19import java.nio.ByteBuffer;
20import java.nio.FloatBuffer;
21
22import com.badlogic.gdx.Gdx;
23import com.badlogic.gdx.graphics.GL20;
24import com.badlogic.gdx.graphics.VertexAttribute;
25import com.badlogic.gdx.graphics.VertexAttributes;
26import com.badlogic.gdx.utils.BufferUtils;
27import com.badlogic.gdx.utils.GdxRuntimeException;
28
29/** <p>
30 * A {@link VertexData} implementation based on OpenGL vertex buffer objects.
31 * </p>
32 *
33 * <p>
34 * If the OpenGL ES context was lost you can call {@link #invalidate()} to recreate a new OpenGL vertex buffer object. This class
35 * can be used seamlessly with OpenGL ES 1.x and 2.0.
36 * </p>
37 *
38 * <p>
39 * In case OpenGL ES 2.0 is used in the application the data is bound via glVertexAttribPointer() according to the attribute
40 * aliases specified via {@link VertexAttributes} in the constructor.
41 * </p>
42 *
43 * <p>
44 * Uses indirect Buffers on Android 1.5/1.6 to fix GC invocation due to leaking PlatformAddress instances.
45 * </p>
46 *
47 * <p>
48 * VertexBufferObjects must be disposed via the {@link #dispose()} method when no longer needed
49 * </p>
50 *
51 * @author mzechner */
52public class VertexBufferObjectSubData implements VertexData {
53	final VertexAttributes attributes;
54	final FloatBuffer buffer;
55	final ByteBuffer byteBuffer;
56	int bufferHandle;
57	final boolean isDirect;
58	final boolean isStatic;
59	final int usage;
60	boolean isDirty = false;
61	boolean isBound = false;
62
63	/** Constructs a new interleaved VertexBufferObject.
64	 *
65	 * @param isStatic whether the vertex data is static.
66	 * @param numVertices the maximum number of vertices
67	 * @param attributes the {@link VertexAttribute}s. */
68	public VertexBufferObjectSubData (boolean isStatic, int numVertices, VertexAttribute... attributes) {
69		this.isStatic = isStatic;
70		this.attributes = new VertexAttributes(attributes);
71		byteBuffer = BufferUtils.newByteBuffer(this.attributes.vertexSize * numVertices);
72		isDirect = true;
73
74		usage = isStatic ? GL20.GL_STATIC_DRAW : GL20.GL_DYNAMIC_DRAW;
75		buffer = byteBuffer.asFloatBuffer();
76		bufferHandle = createBufferObject();
77		buffer.flip();
78		byteBuffer.flip();
79	}
80
81	private int createBufferObject () {
82		int result = Gdx.gl20.glGenBuffer();
83		Gdx.gl20.glBindBuffer(GL20.GL_ARRAY_BUFFER, result);
84		Gdx.gl20.glBufferData(GL20.GL_ARRAY_BUFFER, byteBuffer.capacity(), null, usage);
85		Gdx.gl20.glBindBuffer(GL20.GL_ARRAY_BUFFER, 0);
86		return result;
87	}
88
89	@Override
90	public VertexAttributes getAttributes () {
91		return attributes;
92	}
93
94	@Override
95	public int getNumVertices () {
96		return buffer.limit() * 4 / attributes.vertexSize;
97	}
98
99	@Override
100	public int getNumMaxVertices () {
101		return byteBuffer.capacity() / attributes.vertexSize;
102	}
103
104	@Override
105	public FloatBuffer getBuffer () {
106		isDirty = true;
107		return buffer;
108	}
109
110	private void bufferChanged () {
111		if (isBound) {
112			Gdx.gl20.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, byteBuffer.limit(), byteBuffer);
113			isDirty = false;
114		}
115	}
116
117	@Override
118	public void setVertices (float[] vertices, int offset, int count) {
119		isDirty = true;
120		if (isDirect) {
121			BufferUtils.copy(vertices, byteBuffer, count, offset);
122			buffer.position(0);
123			buffer.limit(count);
124		} else {
125			buffer.clear();
126			buffer.put(vertices, offset, count);
127			buffer.flip();
128			byteBuffer.position(0);
129			byteBuffer.limit(buffer.limit() << 2);
130		}
131
132		bufferChanged();
133	}
134
135	@Override
136	public void updateVertices (int targetOffset, float[] vertices, int sourceOffset, int count) {
137		isDirty = true;
138		if (isDirect) {
139			final int pos = byteBuffer.position();
140			byteBuffer.position(targetOffset * 4);
141			BufferUtils.copy(vertices, sourceOffset, count, byteBuffer);
142			byteBuffer.position(pos);
143		} else
144			throw new GdxRuntimeException("Buffer must be allocated direct."); // Should never happen
145
146		bufferChanged();
147	}
148
149	/** Binds this VertexBufferObject for rendering via glDrawArrays or glDrawElements
150	 *
151	 * @param shader the shader */
152	@Override
153	public void bind (final ShaderProgram shader) {
154		bind(shader, null);
155	}
156
157	@Override
158	public void bind (final ShaderProgram shader, final int[] locations) {
159		final GL20 gl = Gdx.gl20;
160
161		gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, bufferHandle);
162		if (isDirty) {
163			byteBuffer.limit(buffer.limit() * 4);
164			gl.glBufferData(GL20.GL_ARRAY_BUFFER, byteBuffer.limit(), byteBuffer, usage);
165			isDirty = false;
166		}
167
168		final int numAttributes = attributes.size();
169		if (locations == null) {
170			for (int i = 0; i < numAttributes; i++) {
171				final VertexAttribute attribute = attributes.get(i);
172				final int location = shader.getAttributeLocation(attribute.alias);
173				if (location < 0) continue;
174				shader.enableVertexAttribute(location);
175
176				shader.setVertexAttribute(location, attribute.numComponents, attribute.type, attribute.normalized, attributes.vertexSize,
177						attribute.offset);
178			}
179		} else {
180			for (int i = 0; i < numAttributes; i++) {
181				final VertexAttribute attribute = attributes.get(i);
182				final int location = locations[i];
183				if (location < 0) continue;
184				shader.enableVertexAttribute(location);
185
186				shader.setVertexAttribute(location, attribute.numComponents, attribute.type, attribute.normalized, attributes.vertexSize,
187						attribute.offset);
188			}
189		}
190		isBound = true;
191	}
192
193	/** Unbinds this VertexBufferObject.
194	 *
195	 * @param shader the shader */
196	@Override
197	public void unbind (final ShaderProgram shader) {
198		unbind(shader, null);
199	}
200
201	@Override
202	public void unbind (final ShaderProgram shader, final int[] locations) {
203		final GL20 gl = Gdx.gl20;
204		final int numAttributes = attributes.size();
205		if (locations == null) {
206			for (int i = 0; i < numAttributes; i++) {
207				shader.disableVertexAttribute(attributes.get(i).alias);
208			}
209		} else {
210			for (int i = 0; i < numAttributes; i++) {
211				final int location = locations[i];
212				if (location >= 0) shader.disableVertexAttribute(location);
213			}
214		}
215		gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, 0);
216		isBound = false;
217	}
218
219	/** Invalidates the VertexBufferObject so a new OpenGL buffer handle is created. Use this in case of a context loss. */
220	public void invalidate () {
221		bufferHandle = createBufferObject();
222		isDirty = true;
223	}
224
225	/** Disposes of all resources this VertexBufferObject uses. */
226	@Override
227	public void dispose () {
228		GL20 gl = Gdx.gl20;
229		gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, 0);
230		gl.glDeleteBuffer(bufferHandle);
231		bufferHandle = 0;
232	}
233
234	/** Returns the VBO handle
235	 * @return the VBO handle */
236	public int getBufferHandle () {
237		return bufferHandle;
238	}
239}
240