diff options
Diffstat (limited to 'engine/src/android/com/jme3/renderer')
3 files changed, 3262 insertions, 0 deletions
diff --git a/engine/src/android/com/jme3/renderer/android/Android22Workaround.java b/engine/src/android/com/jme3/renderer/android/Android22Workaround.java new file mode 100644 index 0000000..9c5bf58 --- /dev/null +++ b/engine/src/android/com/jme3/renderer/android/Android22Workaround.java @@ -0,0 +1,14 @@ +package com.jme3.renderer.android; + +import android.opengl.GLES20; + +public class Android22Workaround { + public static void glVertexAttribPointer(int location, int components, int format, boolean normalize, int stride, int offset){ + GLES20.glVertexAttribPointer(location, + components, + format, + normalize, + stride, + offset); + } +} diff --git a/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java b/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java new file mode 100644 index 0000000..b5a8c14 --- /dev/null +++ b/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java @@ -0,0 +1,2951 @@ +/* + * Copyright (c) 2009-2010 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.android; + +import android.graphics.Bitmap; +import android.opengl.GLES10; +import android.opengl.GLES11; +import android.opengl.GLES20; +import android.os.Build; +import com.jme3.asset.AndroidImageInfo; +import com.jme3.light.LightList; +import com.jme3.material.RenderState; +import com.jme3.math.*; +import com.jme3.renderer.*; +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.shader.Attribute; +import com.jme3.shader.Shader; +import com.jme3.shader.Shader.ShaderSource; +import com.jme3.shader.Shader.ShaderType; +import com.jme3.shader.Uniform; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.FrameBuffer.RenderBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapAxis; +import com.jme3.util.BufferUtils; +import com.jme3.util.ListMap; +import com.jme3.util.NativeObjectManager; +import com.jme3.util.SafeArrayList; +import java.nio.*; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.microedition.khronos.opengles.GL10; + +public class OGLESShaderRenderer implements Renderer { + + private static final Logger logger = Logger.getLogger(OGLESShaderRenderer.class.getName()); + private static final boolean VALIDATE_SHADER = false; + private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); + private final StringBuilder stringBuf = new StringBuilder(250); + private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); + private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16); + private final RenderContext context = new RenderContext(); + private final NativeObjectManager objManager = new NativeObjectManager(); + private final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class); + // current state + private Shader boundShader; + private int initialDrawBuf, initialReadBuf; + private int glslVer; + private int vertexTextureUnits; + private int fragTextureUnits; + private int vertexUniforms; + private int fragUniforms; + private int vertexAttribs; + private int maxFBOSamples; + private int maxFBOAttachs; + private int maxMRTFBOAttachs; + private int maxRBSize; + private int maxTexSize; + private int maxCubeTexSize; + private int maxVertCount; + private int maxTriCount; + private boolean tdc; + private FrameBuffer lastFb = null; + private final Statistics statistics = new Statistics(); + private int vpX, vpY, vpW, vpH; + private int clipX, clipY, clipW, clipH; + //private final GL10 gl; + private boolean powerVr = false; + private boolean powerOf2 = false; + private boolean verboseLogging = false; + private boolean useVBO = false; + private boolean checkErrors = true; + + public OGLESShaderRenderer() { + } + + public void setUseVA(boolean value) { + logger.log(Level.INFO, "use_VBO [{0}] -> [{1}]", new Object[]{useVBO, !value}); + useVBO = !value; + } + + public void setVerboseLogging(boolean value) { + logger.log(Level.INFO, "verboseLogging [{0}] -> [{1}]", new Object[]{verboseLogging, value}); + verboseLogging = value; + } + + protected void updateNameBuffer() { + int len = stringBuf.length(); + + nameBuf.position(0); + nameBuf.limit(len); + for (int i = 0; i < len; i++) { + nameBuf.put((byte) stringBuf.charAt(i)); + } + + nameBuf.rewind(); + } + + private void checkGLError() { + if (!checkErrors) return; + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + throw new RendererException("OpenGL Error " + error); + } + } + + private boolean log(String message) { + logger.info(message); + return true; + } + + public Statistics getStatistics() { + return statistics; + } + + public EnumSet<Caps> getCaps() { + return caps; + } + + public void initialize() { + + logger.log(Level.INFO, "Vendor: {0}", GLES20.glGetString(GLES20.GL_VENDOR)); + logger.log(Level.INFO, "Renderer: {0}", GLES20.glGetString(GLES20.GL_RENDERER)); + logger.log(Level.INFO, "Version: {0}", GLES20.glGetString(GLES20.GL_VERSION)); + + powerVr = GLES20.glGetString(GLES20.GL_RENDERER).contains("PowerVR"); + + String versionStr = GLES20.glGetString(GLES20.GL_SHADING_LANGUAGE_VERSION); + logger.log(Level.INFO, "GLES20.Shading Language Version: {0}", versionStr); + if (versionStr == null || versionStr.equals("")) { + glslVer = -1; + throw new UnsupportedOperationException("GLSL and OpenGL2 is " + + "required for the OpenGL ES " + + "renderer!"); + } + + // Fix issue in TestRenderToMemory when GL_FRONT is the main + // buffer being used. + +// initialDrawBuf = GLES20.glGetIntegeri(GLES20.GL_DRAW_BUFFER); +// initialReadBuf = GLES20.glGetIntegeri(GLES20.GL_READ_BUFFER); + + String openGlEsStr = "OpenGL ES GLSL ES "; + int spaceIdx = versionStr.indexOf(" ", openGlEsStr.length()); + if (spaceIdx >= 1) { + versionStr = versionStr.substring(openGlEsStr.length(), spaceIdx).trim(); + }else{ + versionStr = versionStr.substring(openGlEsStr.length()).trim(); + } + + float version = Float.parseFloat(versionStr); + glslVer = (int) (version * 100); + + switch (glslVer) { + // TODO: When new versions of OpenGL ES shader language come out, + // update this. + default: + caps.add(Caps.GLSL100); + break; + } + + if (!caps.contains(Caps.GLSL100)) { + logger.info("Force-adding GLSL100 support, since OpenGL2 is supported."); + caps.add(Caps.GLSL100); + } + + GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16); + vertexTextureUnits = intBuf16.get(0); + logger.log(Level.INFO, "VTF Units: {0}", vertexTextureUnits); + if (vertexTextureUnits > 0) { + caps.add(Caps.VertexTextureFetch); + } + + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16); + fragTextureUnits = intBuf16.get(0); + logger.log(Level.INFO, "Texture Units: {0}", fragTextureUnits); + /* + GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16); + vertexUniforms = intBuf16.get(0); + logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); + + GLES20.glGetIntegerv(GLES20.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16); + fragUniforms = intBuf16.get(0); + logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); + */ + + GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_ATTRIBS, intBuf16); + vertexAttribs = intBuf16.get(0); + logger.log(Level.INFO, "Vertex Attributes: {0}", vertexAttribs); + + /* + GLES20.glGetIntegerv(GLES20.GL_MAX_VARYING_FLOATS, intBuf16); + int varyingFloats = intBuf16.get(0); + logger.log(Level.FINER, "Varying Floats: {0}", varyingFloats); + */ + + GLES20.glGetIntegerv(GLES20.GL_SUBPIXEL_BITS, intBuf16); + int subpixelBits = intBuf16.get(0); + logger.log(Level.INFO, "Subpixel Bits: {0}", subpixelBits); + /* + GLES20.glGetIntegerv(GLES20.GL_MAX_ELEMENTS_VERTICES, intBuf16); + maxVertCount = intBuf16.get(0); + logger.log(Level.FINER, "Preferred Batch Vertex Count: {0}", maxVertCount); + + GLES20.glGetIntegerv(GLES20.GL_MAX_ELEMENTS_INDICES, intBuf16); + maxTriCount = intBuf16.get(0); + logger.log(Level.FINER, "Preferred Batch Index Count: {0}", maxTriCount); + */ + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, intBuf16); + maxTexSize = intBuf16.get(0); + logger.log(Level.INFO, "Maximum Texture Resolution: {0}", maxTexSize); + + GLES20.glGetIntegerv(GLES20.GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16); + maxCubeTexSize = intBuf16.get(0); + logger.log(Level.INFO, "Maximum CubeMap Resolution: {0}", maxCubeTexSize); + + + /* + if (ctxCaps.GL_ARB_color_buffer_float){ + // XXX: Require both 16 and 32 bit float support for FloatColorBuffer. + if (ctxCaps.GL_ARB_half_float_pixel){ + caps.add(Caps.FloatColorBuffer); + } + } + + if (ctxCaps.GL_ARB_depth_buffer_float){ + caps.add(Caps.FloatDepthBuffer); + } + + if (ctxCaps.GL_ARB_draw_instanced) + caps.add(Caps.MeshInstancing); + + if (ctxCaps.GL_ARB_fragment_program) + caps.add(Caps.ARBprogram); + + if (ctxCaps.GL_ARB_texture_buffer_object) + caps.add(Caps.TextureBuffer); + + if (ctxCaps.GL_ARB_texture_float){ + if (ctxCaps.GL_ARB_half_float_pixel){ + caps.add(Caps.FloatTexture); + } + } + + if (ctxCaps.GL_ARB_vertex_array_object) + caps.add(Caps.VertexBufferArray); + + boolean latc = ctxCaps.GL_EXT_texture_compression_latc; + boolean atdc = ctxCaps.GL_ATI_texture_compression_3dc; + if (latc || atdc){ + caps.add(Caps.TextureCompressionLATC); + if (atdc && !latc){ + tdc = true; + } + } + + if (ctxCaps.GL_EXT_packed_float){ + caps.add(Caps.PackedFloatColorBuffer); + if (ctxCaps.GL_ARB_half_float_pixel){ + // because textures are usually uploaded as RGB16F + // need half-float pixel + caps.add(Caps.PackedFloatTexture); + } + } + + if (ctxCaps.GL_EXT_texture_array) + caps.add(Caps.TextureArray); + + if (ctxCaps.GL_EXT_texture_shared_exponent) + caps.add(Caps.SharedExponentTexture); + + if (ctxCaps.GL_EXT_framebuffer_object){ + caps.add(Caps.FrameBuffer); + + glGetInteger(GL_MAX_RENDERBUFFER_SIZE_EXT, intBuf16); + maxRBSize = intBuf16.get(0); + logger.log(Level.FINER, "FBO RB Max Size: {0}", maxRBSize); + + glGetInteger(GL_MAX_COLOR_ATTACHMENTS_EXT, intBuf16); + maxFBOAttachs = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max renderbuffers: {0}", maxFBOAttachs); + + if (ctxCaps.GL_EXT_framebuffer_multisample){ + caps.add(Caps.FrameBufferMultisample); + + glGetInteger(GL_MAX_SAMPLES_EXT, intBuf16); + maxFBOSamples = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max Samples: {0}", maxFBOSamples); + } + + if (ctxCaps.GL_ARB_draw_buffers){ + caps.add(Caps.FrameBufferMRT); + glGetInteger(ARBDrawBuffers.GL_MAX_DRAW_BUFFERS_ARB, intBuf16); + maxMRTFBOAttachs = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max MRT renderbuffers: {0}", maxMRTFBOAttachs); + } + } + + if (ctxCaps.GL_ARB_multisample){ + glGetInteger(ARBMultisample.GL_SAMPLE_BUFFERS_ARB, intBuf16); + boolean available = intBuf16.get(0) != 0; + glGetInteger(ARBMultisample.GL_SAMPLES_ARB, intBuf16); + int samples = intBuf16.get(0); + logger.log(Level.FINER, "Samples: {0}", samples); + boolean enabled = glIsEnabled(ARBMultisample.GL_MULTISAMPLE_ARB); + if (samples > 0 && available && !enabled){ + glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); + } + } + */ + + String extensions = GLES20.glGetString(GLES20.GL_EXTENSIONS); + logger.log(Level.INFO, "GL_EXTENSIONS: {0}", extensions); + + GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, intBuf16); + for (int i = 0; i < intBuf16.limit(); i++) { + logger.log(Level.INFO, "Compressed Texture Formats: {0}", intBuf16.get(i)); + } + + if (extensions.contains("GL_OES_texture_npot")) { + powerOf2 = true; + } + + applyRenderState(RenderState.DEFAULT); +// GLES20.glClearDepthf(1.0f); + + if (verboseLogging) { + logger.info("GLES20.glDisable(GL10.GL_DITHER)"); + } + + GLES20.glDisable(GL10.GL_DITHER); + + checkGLError(); + + if (verboseLogging) { + logger.info("GLES20.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST)"); + } + + //It seems that GL10.GL_PERSPECTIVE_CORRECTION_HINT gives invalid_enum error on android. +// GLES20.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); + +// checkGLError(); + + useVBO = false; + + // NOTE: SDK_INT is only available since 1.6, + // but for jME3 it doesn't matter since android versions 1.5 and below + // are not supported. + if (Build.VERSION.SDK_INT >= 9){ + useVBO = true; + } + + logger.log(Level.INFO, "Caps: {0}", caps); + } + + /** + * <code>resetGLObjects</code> should be called when die GLView gets recreated to reset all GPU objects + */ + public void resetGLObjects() { + objManager.resetObjects(); + statistics.clearMemory(); + boundShader = null; + lastFb = null; + context.reset(); + } + + public void cleanup() { + objManager.deleteAllObjects(this); + statistics.clearMemory(); + } + + private void checkCap(Caps cap) { + if (!caps.contains(cap)) { + throw new UnsupportedOperationException("Required capability missing: " + cap.name()); + } + } + + /*********************************************************************\ + |* Render State *| + \*********************************************************************/ + public void setDepthRange(float start, float end) { + + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glDepthRangef({0}, {1})", new Object[]{start, end}); + } + GLES20.glDepthRangef(start, end); + checkGLError(); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + int bits = 0; + if (color) { + bits = GLES20.GL_COLOR_BUFFER_BIT; + } + if (depth) { + bits |= GLES20.GL_DEPTH_BUFFER_BIT; + } + if (stencil) { + bits |= GLES20.GL_STENCIL_BUFFER_BIT; + } + if (bits != 0) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glClear(color={0}, depth={1}, stencil={2})", new Object[]{color, depth, stencil}); + } + GLES20.glClear(bits); + checkGLError(); + } + } + + public void setBackgroundColor(ColorRGBA color) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glClearColor({0}, {1}, {2}, {3})", new Object[]{color.r, color.g, color.b, color.a}); + } + GLES20.glClearColor(color.r, color.g, color.b, color.a); + checkGLError(); + } + + public void applyRenderState(RenderState state) { + /* + if (state.isWireframe() && !context.wireframe){ + GLES20.glPolygonMode(GLES20.GL_FRONT_AND_BACK, GLES20.GL_LINE); + context.wireframe = true; + }else if (!state.isWireframe() && context.wireframe){ + GLES20.glPolygonMode(GLES20.GL_FRONT_AND_BACK, GLES20.GL_FILL); + context.wireframe = false; + } + */ + if (state.isDepthTest() && !context.depthTestEnabled) { + if (verboseLogging) { + logger.info("GLES20.glEnable(GLES20.GL_DEPTH_TEST)"); + } + GLES20.glEnable(GLES20.GL_DEPTH_TEST); + checkGLError(); + if (verboseLogging) { + logger.info("GLES20.glDepthFunc(GLES20.GL_LEQUAL)"); + } + GLES20.glDepthFunc(GLES20.GL_LEQUAL); + checkGLError(); + context.depthTestEnabled = true; + } else if (!state.isDepthTest() && context.depthTestEnabled) { + if (verboseLogging) { + logger.info("GLES20.glDisable(GLES20.GL_DEPTH_TEST)"); + } + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + checkGLError(); + context.depthTestEnabled = false; + } + if (state.isAlphaTest() && !context.alphaTestEnabled) { +// GLES20.glEnable(GLES20.GL_ALPHA_TEST); +// GLES20.glAlphaFunc(GLES20.GL_GREATER, state.getAlphaFallOff()); + context.alphaTestEnabled = true; + } else if (!state.isAlphaTest() && context.alphaTestEnabled) { +// GLES20.glDisable(GLES20.GL_ALPHA_TEST); + context.alphaTestEnabled = false; + } + if (state.isDepthWrite() && !context.depthWriteEnabled) { + if (verboseLogging) { + logger.info("GLES20.glDepthMask(true)"); + } + GLES20.glDepthMask(true); + checkGLError(); + context.depthWriteEnabled = true; + } else if (!state.isDepthWrite() && context.depthWriteEnabled) { + if (verboseLogging) { + logger.info("GLES20.glDepthMask(false)"); + } + GLES20.glDepthMask(false); + checkGLError(); + context.depthWriteEnabled = false; + } + if (state.isColorWrite() && !context.colorWriteEnabled) { + if (verboseLogging) { + logger.info("GLES20.glColorMask(true, true, true, true)"); + } + GLES20.glColorMask(true, true, true, true); + checkGLError(); + context.colorWriteEnabled = true; + } else if (!state.isColorWrite() && context.colorWriteEnabled) { + if (verboseLogging) { + logger.info("GLES20.glColorMask(false, false, false, false)"); + } + GLES20.glColorMask(false, false, false, false); + checkGLError(); + context.colorWriteEnabled = false; + } + if (state.isPointSprite() && !context.pointSprite) { +// GLES20.glEnable(GLES20.GL_POINT_SPRITE); +// GLES20.glTexEnvi(GLES20.GL_POINT_SPRITE, GLES20.GL_COORD_REPLACE, GLES20.GL_TRUE); +// GLES20.glEnable(GLES20.GL_VERTEX_PROGRAM_POINT_SIZE); +// GLES20.glPointParameterf(GLES20.GL_POINT_SIZE_MIN, 1.0f); + } else if (!state.isPointSprite() && context.pointSprite) { +// GLES20.glDisable(GLES20.GL_POINT_SPRITE); + } + + if (state.isPolyOffset()) { + if (!context.polyOffsetEnabled) { + if (verboseLogging) { + logger.info("GLES20.glEnable(GLES20.GL_POLYGON_OFFSET_FILL)"); + } + GLES20.glEnable(GLES20.GL_POLYGON_OFFSET_FILL); + checkGLError(); + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glPolygonOffset({0}, {1})", new Object[]{state.getPolyOffsetFactor(), state.getPolyOffsetUnits()}); + } + GLES20.glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + checkGLError(); + context.polyOffsetEnabled = true; + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } else { + if (state.getPolyOffsetFactor() != context.polyOffsetFactor + || state.getPolyOffsetUnits() != context.polyOffsetUnits) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glPolygonOffset({0}, {1})", new Object[]{state.getPolyOffsetFactor(), state.getPolyOffsetUnits()}); + } + GLES20.glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + checkGLError(); + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } + } + } else { + if (context.polyOffsetEnabled) { + if (verboseLogging) { + logger.info("GLES20.glDisable(GLES20.GL_POLYGON_OFFSET_FILL)"); + } + GLES20.glDisable(GLES20.GL_POLYGON_OFFSET_FILL); + checkGLError(); + context.polyOffsetEnabled = false; + context.polyOffsetFactor = 0; + context.polyOffsetUnits = 0; + } + } + if (state.getFaceCullMode() != context.cullMode) { + if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { + if (verboseLogging) { + logger.info("GLES20.glDisable(GLES20.GL_CULL_FACE)"); + } + GLES20.glDisable(GLES20.GL_CULL_FACE); + } else { + if (verboseLogging) { + logger.info("GLES20.glEnable(GLES20.GL_CULL_FACE)"); + } + GLES20.glEnable(GLES20.GL_CULL_FACE); + } + + checkGLError(); + + switch (state.getFaceCullMode()) { + case Off: + break; + case Back: + if (verboseLogging) { + logger.info("GLES20.glCullFace(GLES20.GL_BACK)"); + } + GLES20.glCullFace(GLES20.GL_BACK); + break; + case Front: + if (verboseLogging) { + logger.info("GLES20.glCullFace(GLES20.GL_FRONT)"); + } + GLES20.glCullFace(GLES20.GL_FRONT); + break; + case FrontAndBack: + if (verboseLogging) { + logger.info("GLES20.glCullFace(GLES20.GL_FRONT_AND_BACK)"); + } + GLES20.glCullFace(GLES20.GL_FRONT_AND_BACK); + break; + default: + throw new UnsupportedOperationException("Unrecognized face cull mode: " + + state.getFaceCullMode()); + } + + checkGLError(); + + context.cullMode = state.getFaceCullMode(); + } + + if (state.getBlendMode() != context.blendMode) { + if (state.getBlendMode() == RenderState.BlendMode.Off) { + if (verboseLogging) { + logger.info("GLES20.glDisable(GLES20.GL_BLEND)"); + } + GLES20.glDisable(GLES20.GL_BLEND); + } else { + if (verboseLogging) { + logger.info("GLES20.glEnable(GLES20.GL_BLEND)"); + } + GLES20.glEnable(GLES20.GL_BLEND); + switch (state.getBlendMode()) { + case Off: + break; + case Additive: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE)"); + } + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE); + break; + case AlphaAdditive: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE)"); + } + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE); + break; + case Color: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_COLOR)"); + } + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_COLOR); + break; + case Alpha: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA)"); + } + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); + break; + case PremultAlpha: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA)"); + } + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); + break; + case Modulate: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_ZERO)"); + } + GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_ZERO); + break; + case ModulateX2: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_SRC_COLOR)"); + } + GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_SRC_COLOR); + break; + default: + throw new UnsupportedOperationException("Unrecognized blend mode: " + + state.getBlendMode()); + } + } + + checkGLError(); + + context.blendMode = state.getBlendMode(); + } + } + + /*********************************************************************\ + |* Camera and World transforms *| + \*********************************************************************/ + public void setViewPort(int x, int y, int w, int h) { + if (x != vpX || vpY != y || vpW != w || vpH != h) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glViewport({0}, {1}, {2}, {3})", new Object[]{x, y, w, h}); + } + GLES20.glViewport(x, y, w, h); + checkGLError(); + vpX = x; + vpY = y; + vpW = w; + vpH = h; + } + } + + public void setClipRect(int x, int y, int width, int height) { + if (!context.clipRectEnabled) { + if (verboseLogging) { + logger.info("GLES20.glEnable(GLES20.GL_SCISSOR_TEST)"); + } + GLES20.glEnable(GLES20.GL_SCISSOR_TEST); + checkGLError(); + context.clipRectEnabled = true; + } + if (clipX != x || clipY != y || clipW != width || clipH != height) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glScissor({0}, {1}, {2}, {3})", new Object[]{x, y, width, height}); + } + GLES20.glScissor(x, y, width, height); + clipX = x; + clipY = y; + clipW = width; + clipH = height; + checkGLError(); + } + } + + public void clearClipRect() { + if (context.clipRectEnabled) { + if (verboseLogging) { + logger.info("GLES20.glDisable(GLES20.GL_SCISSOR_TEST)"); + } + GLES20.glDisable(GLES20.GL_SCISSOR_TEST); + checkGLError(); + context.clipRectEnabled = false; + + clipX = 0; + clipY = 0; + clipW = 0; + clipH = 0; + } + } + + public void onFrame() { + if (!checkErrors){ + int error = GLES20.glGetError(); + if (error != GLES20.GL_NO_ERROR){ + throw new RendererException("OpenGL Error " + error + ". Enable error checking for more info."); + } + } + objManager.deleteUnused(this); +// statistics.clearFrame(); + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + } + + /*********************************************************************\ + |* Shaders *| + \*********************************************************************/ + protected void updateUniformLocation(Shader shader, Uniform uniform) { + stringBuf.setLength(0); + stringBuf.append(uniform.getName()).append('\0'); + updateNameBuffer(); + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glGetUniformLocation({0}, {1})", new Object[]{shader.getId(), uniform.getName()}); + } + int loc = GLES20.glGetUniformLocation(shader.getId(), uniform.getName()); + checkGLError(); + if (loc < 0) { + uniform.setLocation(-1); + // uniform is not declared in shader + if (verboseLogging) { + logger.log(Level.WARNING, "Uniform [{0}] is not declared in shader.", uniform.getName()); + } + } else { + uniform.setLocation(loc); + } + } + + protected void updateUniform(Shader shader, Uniform uniform) { + int shaderId = shader.getId(); + + assert uniform.getName() != null; + assert shader.getId() > 0; + + if (context.boundShaderProgram != shaderId) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glUseProgram({0})", shaderId); + } + GLES20.glUseProgram(shaderId); + checkGLError(); + statistics.onShaderUse(shader, true); + boundShader = shader; + context.boundShaderProgram = shaderId; + } else { + statistics.onShaderUse(shader, false); + } + + int loc = uniform.getLocation(); + if (loc == -1) { + if (verboseLogging) { + logger.log(Level.WARNING, "no location for uniform [{0}]", uniform.getName()); + } + return; + } + + if (loc == -2) { + // get uniform location + updateUniformLocation(shader, uniform); + if (uniform.getLocation() == -1) { + // not declared, ignore + + if (verboseLogging) { + logger.log(Level.WARNING, "not declared uniform: [{0}]", uniform.getName()); + } + + uniform.clearUpdateNeeded(); + return; + } + loc = uniform.getLocation(); + } + + if (uniform.getVarType() == null) { + logger.warning("value is not set yet."); + return; // value not set yet.. + } + + statistics.onUniformSet(); + + uniform.clearUpdateNeeded(); + FloatBuffer fb; + switch (uniform.getVarType()) { + case Float: + if (verboseLogging) { + logger.info("GLES20.glUniform1f set Float. " + uniform.getName()); + } + Float f = (Float) uniform.getValue(); + GLES20.glUniform1f(loc, f.floatValue()); + break; + case Vector2: + if (verboseLogging) { + logger.info("GLES20.glUniform2f set Vector2. " + uniform.getName()); + } + Vector2f v2 = (Vector2f) uniform.getValue(); + GLES20.glUniform2f(loc, v2.getX(), v2.getY()); + break; + case Vector3: + if (verboseLogging) { + logger.info("GLES20.glUniform3f set Vector3. " + uniform.getName()); + } + Vector3f v3 = (Vector3f) uniform.getValue(); + GLES20.glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); + break; + case Vector4: + if (verboseLogging) { + logger.info("GLES20.glUniform4f set Vector4." + uniform.getName()); + } + Object val = uniform.getValue(); + if (val instanceof ColorRGBA) { + ColorRGBA c = (ColorRGBA) val; + GLES20.glUniform4f(loc, c.r, c.g, c.b, c.a); + } else { + Quaternion c = (Quaternion) uniform.getValue(); + GLES20.glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); + } + break; + case Boolean: + if (verboseLogging) { + logger.info("GLES20.glUniform1i set Boolean." + uniform.getName()); + } + Boolean b = (Boolean) uniform.getValue(); + GLES20.glUniform1i(loc, b.booleanValue() ? GLES20.GL_TRUE : GLES20.GL_FALSE); + break; + case Matrix3: + if (verboseLogging) { + logger.info("GLES20.glUniformMatrix3fv set Matrix3." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 9; + GLES20.glUniformMatrix3fv(loc, 1, false, fb); + break; + case Matrix4: + if (verboseLogging) { + logger.info("GLES20.glUniformMatrix4fv set Matrix4." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 16; + GLES20.glUniformMatrix4fv(loc, 1, false, fb); + break; + case FloatArray: + if (verboseLogging) { + logger.info("GLES20.glUniform1fv set FloatArray." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform1fv(loc, fb.capacity(), fb); + break; + case Vector2Array: + if (verboseLogging) { + logger.info("GLES20.glUniform2fv set Vector2Array." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform2fv(loc, fb.capacity() / 2, fb); + break; + case Vector3Array: + if (verboseLogging) { + logger.info("GLES20.glUniform3fv set Vector3Array." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform3fv(loc, fb.capacity() / 3, fb); + break; + case Vector4Array: + if (verboseLogging) { + logger.info("GLES20.glUniform4fv set Vector4Array." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform4fv(loc, fb.capacity() / 4, fb); + break; + case Matrix4Array: + if (verboseLogging) { + logger.info("GLES20.glUniform4fv set Matrix4Array." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniformMatrix4fv(loc, fb.capacity() / 16, false, fb); + break; + case Int: + if (verboseLogging) { + logger.info("GLES20.glUniform1i set Int." + uniform.getName()); + } + Integer i = (Integer) uniform.getValue(); + GLES20.glUniform1i(loc, i.intValue()); + break; + default: + throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); + } + checkGLError(); + } + + protected void updateShaderUniforms(Shader shader) { + ListMap<String, Uniform> uniforms = shader.getUniformMap(); +// for (Uniform uniform : shader.getUniforms()){ + for (int i = 0; i < uniforms.size(); i++) { + Uniform uniform = uniforms.getValue(i); + if (uniform.isUpdateNeeded()) { + updateUniform(shader, uniform); + } + } + } + + protected void resetUniformLocations(Shader shader) { + ListMap<String, Uniform> uniforms = shader.getUniformMap(); +// for (Uniform uniform : shader.getUniforms()){ + for (int i = 0; i < uniforms.size(); i++) { + Uniform uniform = uniforms.getValue(i); + uniform.reset(); // e.g check location again + } + } + + /* + * (Non-javadoc) + * Only used for fixed-function. Ignored. + */ + public void setLighting(LightList list) { + } + + public int convertShaderType(ShaderType type) { + switch (type) { + case Fragment: + return GLES20.GL_FRAGMENT_SHADER; + case Vertex: + return GLES20.GL_VERTEX_SHADER; +// case Geometry: +// return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB; + default: + throw new RuntimeException("Unrecognized shader type."); + } + } + + public void updateShaderSourceData(ShaderSource source, String language) { + int id = source.getId(); + if (id == -1) { + // create id + if (verboseLogging) { + logger.info("GLES20.glCreateShader(" + source.getType() + ")"); + } + id = GLES20.glCreateShader(convertShaderType(source.getType())); + checkGLError(); + if (id <= 0) { + throw new RendererException("Invalid ID received when trying to create shader."); + } + + source.setId(id); + } + + // upload shader source + // merge the defines and source code + byte[] versionData = new byte[]{};//"#version 140\n".getBytes(); +// versionData = "#define INSTANCING 1\n".getBytes(); + byte[] definesCodeData = source.getDefines().getBytes(); + byte[] sourceCodeData = source.getSource().getBytes(); + ByteBuffer codeBuf = BufferUtils.createByteBuffer(versionData.length + + definesCodeData.length + + sourceCodeData.length); + codeBuf.put(versionData); + codeBuf.put(definesCodeData); + codeBuf.put(sourceCodeData); + codeBuf.flip(); + + if (verboseLogging) { + logger.info("GLES20.glShaderSource(" + id + ")"); + } + + if (powerVr && source.getType() == ShaderType.Vertex) { + // XXX: This is to fix a bug in old PowerVR, remove + // when no longer applicable. + GLES20.glShaderSource( + id, source.getDefines() + + source.getSource()); + } else { + GLES20.glShaderSource( + id, + "precision mediump float;\n" + + source.getDefines() + + source.getSource()); + } + + checkGLError(); + + if (verboseLogging) { + logger.info("GLES20.glCompileShader(" + id + ")"); + } + + GLES20.glCompileShader(id); + + checkGLError(); + + if (verboseLogging) { + logger.info("GLES20.glGetShaderiv(" + id + ", GLES20.GL_COMPILE_STATUS)"); + } + + GLES20.glGetShaderiv(id, GLES20.GL_COMPILE_STATUS, intBuf1); + + checkGLError(); + + boolean compiledOK = intBuf1.get(0) == GLES20.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !compiledOK) { + // even if compile succeeded, check + // log for warnings + if (verboseLogging) { + logger.info("GLES20.glGetShaderiv()"); + } + GLES20.glGetShaderiv(id, GLES20.GL_INFO_LOG_LENGTH, intBuf1); + checkGLError(); + if (verboseLogging) { + logger.info("GLES20.glGetShaderInfoLog(" + id + ")"); + } + infoLog = GLES20.glGetShaderInfoLog(id); + logger.severe("Errooooooooooot(" + id + ")"); + } + + if (compiledOK) { + if (infoLog != null) { + logger.log(Level.INFO, "compile success: " + source.getName() + ", " + infoLog); + } else { + logger.log(Level.FINE, "compile success: " + source.getName()); + } + } else { + logger.log(Level.WARNING, "Bad compile of:\n{0}{1}", + new Object[]{source.getDefines(), source.getSource()}); + if (infoLog != null) { + throw new RendererException("compile error in:" + source + " error:" + infoLog); + } else { + throw new RendererException("compile error in:" + source + " error: <not provided>"); + } + } + + source.clearUpdateNeeded(); + // only usable if compiled + source.setUsable(compiledOK); + if (!compiledOK) { + // make sure to dispose id cause all program's + // shaders will be cleared later. + if (verboseLogging) { + logger.info("GLES20.glDeleteShader(" + id + ")"); + } + GLES20.glDeleteShader(id); + checkGLError(); + } else { + // register for cleanup since the ID is usable + objManager.registerForCleanup(source); + } + } + + public void updateShaderData(Shader shader) { + int id = shader.getId(); + boolean needRegister = false; + if (id == -1) { + // create program + + if (verboseLogging) { + logger.info("GLES20.glCreateProgram()"); + } + + id = GLES20.glCreateProgram(); + + if (id <= 0) { + throw new RendererException("Invalid ID received when trying to create shader program."); + } + + shader.setId(id); + needRegister = true; + } + + for (ShaderSource source : shader.getSources()) { + if (source.isUpdateNeeded()) { + updateShaderSourceData(source, shader.getLanguage()); + // shader has been compiled here + } + + if (!source.isUsable()) { + // it's useless.. just forget about everything.. + shader.setUsable(false); + shader.clearUpdateNeeded(); + return; + } + if (verboseLogging) { + logger.info("GLES20.glAttachShader(" + id + ", " + source.getId() + ")"); + } + + GLES20.glAttachShader(id, source.getId()); + } + + // link shaders to program + if (verboseLogging) { + logger.info("GLES20.glLinkProgram(" + id + ")"); + } + + GLES20.glLinkProgram(id); + + + if (verboseLogging) { + logger.info("GLES20.glGetProgramiv(" + id + ")"); + } + + GLES20.glGetProgramiv(id, GLES20.GL_LINK_STATUS, intBuf1); + + boolean linkOK = intBuf1.get(0) == GLES20.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !linkOK) { + if (verboseLogging) { + logger.info("GLES20.glGetProgramiv(" + id + ", GLES20.GL_INFO_LOG_LENGTH, buffer)"); + } + + GLES20.glGetProgramiv(id, GLES20.GL_INFO_LOG_LENGTH, intBuf1); + + int length = intBuf1.get(0); + if (length > 3) { + // get infos + + if (verboseLogging) { + logger.info("GLES20.glGetProgramInfoLog(" + id + ")"); + } + + infoLog = GLES20.glGetProgramInfoLog(id); + } + } + + if (linkOK) { + if (infoLog != null) { + logger.log(Level.INFO, "shader link success. \n{0}", infoLog); + } else { + logger.fine("shader link success"); + } + } else { + if (infoLog != null) { + throw new RendererException("Shader link failure, shader:" + shader + " info:" + infoLog); + } else { + throw new RendererException("Shader link failure, shader:" + shader + " info: <not provided>"); + } + } + + shader.clearUpdateNeeded(); + if (!linkOK) { + // failure.. forget about everything + shader.resetSources(); + shader.setUsable(false); + deleteShader(shader); + } else { + shader.setUsable(true); + if (needRegister) { + objManager.registerForCleanup(shader); + statistics.onNewShader(); + } else { + // OpenGL spec: uniform locations may change after re-link + resetUniformLocations(shader); + } + } + } + + public void setShader(Shader shader) { + if (verboseLogging) { + logger.info("setShader(" + shader + ")"); + } + + if (shader == null) { + if (context.boundShaderProgram > 0) { + + if (verboseLogging) { + logger.info("GLES20.glUseProgram(0)"); + } + + GLES20.glUseProgram(0); + + statistics.onShaderUse(null, true); + context.boundShaderProgram = 0; + boundShader = null; + } + } else { + if (shader.isUpdateNeeded()) { + updateShaderData(shader); + } + + // NOTE: might want to check if any of the + // sources need an update? + + if (!shader.isUsable()) { + logger.warning("shader is not usable."); + return; + } + + assert shader.getId() > 0; + + updateShaderUniforms(shader); + if (context.boundShaderProgram != shader.getId()) { + if (VALIDATE_SHADER) { + // check if shader can be used + // with current state + if (verboseLogging) { + logger.info("GLES20.glValidateProgram(" + shader.getId() + ")"); + } + + GLES20.glValidateProgram(shader.getId()); + + if (verboseLogging) { + logger.info("GLES20.glGetProgramiv(" + shader.getId() + ", GLES20.GL_VALIDATE_STATUS, buffer)"); + } + + GLES20.glGetProgramiv(shader.getId(), GLES20.GL_VALIDATE_STATUS, intBuf1); + + boolean validateOK = intBuf1.get(0) == GLES20.GL_TRUE; + + if (validateOK) { + logger.fine("shader validate success"); + } else { + logger.warning("shader validate failure"); + } + } + + if (verboseLogging) { + logger.info("GLES20.glUseProgram(" + shader.getId() + ")"); + } + + GLES20.glUseProgram(shader.getId()); + + statistics.onShaderUse(shader, true); + context.boundShaderProgram = shader.getId(); + boundShader = shader; + } else { + statistics.onShaderUse(shader, false); + } + } + } + + public void deleteShaderSource(ShaderSource source) { + if (source.getId() < 0) { + logger.warning("Shader source is not uploaded to GPU, cannot delete."); + return; + } + source.setUsable(false); + source.clearUpdateNeeded(); + + if (verboseLogging) { + logger.info("GLES20.glDeleteShader(" + source.getId() + ")"); + } + + GLES20.glDeleteShader(source.getId()); + source.resetObject(); + } + + public void deleteShader(Shader shader) { + if (shader.getId() == -1) { + logger.warning("Shader is not uploaded to GPU, cannot delete."); + return; + } + for (ShaderSource source : shader.getSources()) { + if (source.getId() != -1) { + + if (verboseLogging) { + logger.info("GLES20.glDetachShader(" + shader.getId() + ", " + source.getId() + ")"); + } + + GLES20.glDetachShader(shader.getId(), source.getId()); + // the next part is done by the GLObjectManager automatically +// glDeleteShader(source.getId()); + } + } + // kill all references so sources can be collected + // if needed. + shader.resetSources(); + + if (verboseLogging) { + logger.info("GLES20.glDeleteProgram(" + shader.getId() + ")"); + } + + GLES20.glDeleteProgram(shader.getId()); + + statistics.onDeleteShader(); + } + + /*********************************************************************\ + |* Framebuffers *| + \*********************************************************************/ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + logger.warning("copyFrameBuffer is not supported."); + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { + logger.warning("copyFrameBuffer is not supported."); + } + /* + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst){ + if (GLContext.getCapabilities().GL_EXT_framebuffer_blit){ + int srcW = 0; + int srcH = 0; + int dstW = 0; + int dstH = 0; + int prevFBO = context.boundFBO; + + if (src != null && src.isUpdateNeeded()) + updateFrameBuffer(src); + + if (dst != null && dst.isUpdateNeeded()) + updateFrameBuffer(dst); + + if (src == null){ + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); + // srcW = viewWidth; + // srcH = viewHeight; + }else{ + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, src.getId()); + srcW = src.getWidth(); + srcH = src.getHeight(); + } + if (dst == null){ + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0); + // dstW = viewWidth; + // dstH = viewHeight; + }else{ + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dst.getId()); + dstW = dst.getWidth(); + dstH = dst.getHeight(); + } + glBlitFramebufferEXT(0, 0, srcW, srcH, + 0, 0, dstW, dstH, + GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, + GL_NEAREST); + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, prevFBO); + try { + checkFrameBufferError(); + } catch (IllegalStateException ex){ + logger.log(Level.SEVERE, "Source FBO:\n{0}", src); + logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst); + throw ex; + } + }else{ + throw new UnsupportedOperationException("EXT_framebuffer_blit required."); + // TODO: support non-blit copies? + } + } + */ + + private void checkFrameBufferError() { + logger.warning("checkFrameBufferError is not supported."); + } + /* + private void checkFrameBufferError() { + int status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + switch (status) { + case GL_FRAMEBUFFER_COMPLETE_EXT: + break; + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + //Choose different formats + throw new IllegalStateException("Framebuffer object format is " + + "unsupported by the video hardware."); + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + throw new IllegalStateException("Framebuffer has erronous attachment."); + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: + throw new IllegalStateException("Framebuffer is missing required attachment."); + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: + throw new IllegalStateException("Framebuffer attachments must have same dimensions."); + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: + throw new IllegalStateException("Framebuffer attachments must have same formats."); + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: + throw new IllegalStateException("Incomplete draw buffer."); + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: + throw new IllegalStateException("Incomplete read buffer."); + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: + throw new IllegalStateException("Incomplete multisample buffer."); + default: + //Programming error; will fail on all hardware + throw new IllegalStateException("Some video driver error " + + "or programming error occured. " + + "Framebuffer object status is invalid. "); + } + } + */ + + private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + logger.warning("updateRenderBuffer is not supported."); + } + /* + private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb){ + int id = rb.getId(); + if (id == -1){ + glGenRenderbuffersEXT(intBuf1); + id = intBuf1.get(0); + rb.setId(id); + } + + if (context.boundRB != id){ + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, id); + context.boundRB = id; + } + + if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize) + throw new UnsupportedOperationException("Resolution "+fb.getWidth()+ + ":"+fb.getHeight()+" is not supported."); + + if (fb.getSamples() > 0 && GLContext.getCapabilities().GL_EXT_framebuffer_multisample){ + int samples = fb.getSamples(); + if (maxFBOSamples < samples){ + samples = maxFBOSamples; + } + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, + samples, + TextureUtil.convertTextureFormat(rb.getFormat()), + fb.getWidth(), + fb.getHeight()); + }else{ + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, + TextureUtil.convertTextureFormat(rb.getFormat()), + fb.getWidth(), + fb.getHeight()); + } + } + */ + + private int convertAttachmentSlot(int attachmentSlot) { + logger.warning("convertAttachmentSlot is not supported."); + return -1; + } + /* + private int convertAttachmentSlot(int attachmentSlot){ + // can also add support for stencil here + if (attachmentSlot == -100){ + return GL_DEPTH_ATTACHMENT_EXT; + }else if (attachmentSlot < 0 || attachmentSlot >= 16){ + throw new UnsupportedOperationException("Invalid FBO attachment slot: "+attachmentSlot); + } + + return GL_COLOR_ATTACHMENT0_EXT + attachmentSlot; + } + */ + + public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) { + logger.warning("updateRenderTexture is not supported."); + } + /* + public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb){ + Texture tex = rb.getTexture(); + Image image = tex.getImage(); + if (image.isUpdateNeeded()) + updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels()); + + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, + convertAttachmentSlot(rb.getSlot()), + convertTextureType(tex.getType()), + image.getId(), + 0); + } + */ + + public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb) { + logger.warning("updateFrameBufferAttachment is not supported."); + } + /* + public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb){ + boolean needAttach; + if (rb.getTexture() == null){ + // if it hasn't been created yet, then attach is required. + needAttach = rb.getId() == -1; + updateRenderBuffer(fb, rb); + }else{ + needAttach = false; + updateRenderTexture(fb, rb); + } + if (needAttach){ + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, + convertAttachmentSlot(rb.getSlot()), + GL_RENDERBUFFER_EXT, + rb.getId()); + } + } + */ + + public void updateFrameBuffer(FrameBuffer fb) { + logger.warning("updateFrameBuffer is not supported."); + } + /* + public void updateFrameBuffer(FrameBuffer fb) { + int id = fb.getId(); + if (id == -1){ + // create FBO + glGenFramebuffersEXT(intBuf1); + id = intBuf1.get(0); + fb.setId(id); + objManager.registerForCleanup(fb); + + statistics.onNewFrameBuffer(); + } + + if (context.boundFBO != id){ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id); + // binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0 + context.boundDrawBuf = 0; + context.boundFBO = id; + } + + FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer(); + if (depthBuf != null){ + updateFrameBufferAttachment(fb, depthBuf); + } + + for (int i = 0; i < fb.getNumColorBuffers(); i++){ + FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i); + updateFrameBufferAttachment(fb, colorBuf); + } + + fb.clearUpdateNeeded(); + } + */ + + public void setMainFrameBufferOverride(FrameBuffer fb){ + } + + public void setFrameBuffer(FrameBuffer fb) { + if (verboseLogging) { + logger.warning("setFrameBuffer is not supported."); + } + } + /* + public void setFrameBuffer(FrameBuffer fb) { + if (lastFb == fb) + return; + + // generate mipmaps for last FB if needed + if (lastFb != null){ + for (int i = 0; i < lastFb.getNumColorBuffers(); i++){ + RenderBuffer rb = lastFb.getColorBuffer(i); + Texture tex = rb.getTexture(); + if (tex != null + && tex.getMinFilter().usesMipMapLevels()){ + setTexture(0, rb.getTexture()); + glGenerateMipmapEXT(convertTextureType(tex.getType())); + } + } + } + + + if (fb == null){ + // unbind any fbos + if (context.boundFBO != 0){ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + statistics.onFrameBufferUse(null, true); + + context.boundFBO = 0; + } + // select back buffer + if (context.boundDrawBuf != -1){ + glDrawBuffer(initialDrawBuf); + context.boundDrawBuf = -1; + } + if (context.boundReadBuf != -1){ + glReadBuffer(initialReadBuf); + context.boundReadBuf = -1; + } + + lastFb = null; + }else{ + if (fb.isUpdateNeeded()) + updateFrameBuffer(fb); + + if (context.boundFBO != fb.getId()){ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb.getId()); + statistics.onFrameBufferUse(fb, true); + + // update viewport to reflect framebuffer's resolution + setViewPort(0, 0, fb.getWidth(), fb.getHeight()); + + context.boundFBO = fb.getId(); + }else{ + statistics.onFrameBufferUse(fb, false); + } + if (fb.getNumColorBuffers() == 0){ + // make sure to select NONE as draw buf + // no color buffer attached. select NONE + if (context.boundDrawBuf != -2){ + glDrawBuffer(GL_NONE); + context.boundDrawBuf = -2; + } + if (context.boundReadBuf != -2){ + glReadBuffer(GL_NONE); + context.boundReadBuf = -2; + } + }else{ + if (fb.isMultiTarget()){ + if (fb.getNumColorBuffers() > maxMRTFBOAttachs) + throw new UnsupportedOperationException("Framebuffer has more" + + " targets than are supported" + + " on the system!"); + + if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()){ + intBuf16.clear(); + for (int i = 0; i < fb.getNumColorBuffers(); i++) + intBuf16.put( GL_COLOR_ATTACHMENT0_EXT + i ); + + intBuf16.flip(); + glDrawBuffers(intBuf16); + context.boundDrawBuf = 100 + fb.getNumColorBuffers(); + } + }else{ + RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); + // select this draw buffer + if (context.boundDrawBuf != rb.getSlot()){ + glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); + context.boundDrawBuf = rb.getSlot(); + } + } + } + + assert fb.getId() >= 0; + assert context.boundFBO == fb.getId(); + lastFb = fb; + } + + try { + checkFrameBufferError(); + } catch (IllegalStateException ex){ + logger.log(Level.SEVERE, "Problem FBO:\n{0}", fb); + throw ex; + } + } + */ + + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + logger.warning("readFrameBuffer is not supported."); + } + /* + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf){ + if (fb != null){ + RenderBuffer rb = fb.getColorBuffer(); + if (rb == null) + throw new IllegalArgumentException("Specified framebuffer" + + " does not have a colorbuffer"); + + setFrameBuffer(fb); + if (context.boundReadBuf != rb.getSlot()){ + glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); + context.boundReadBuf = rb.getSlot(); + } + }else{ + setFrameBuffer(null); + } + + glReadPixels(vpX, vpY, vpW, vpH, GL_RGBA GL_BGRA, GL_UNSIGNED_BYTE, byteBuf); + } + */ + + private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + logger.warning("deleteRenderBuffer is not supported."); + } + /* + private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb){ + intBuf1.put(0, rb.getId()); + glDeleteRenderbuffersEXT(intBuf1); + } + */ + + public void deleteFrameBuffer(FrameBuffer fb) { + logger.warning("deleteFrameBuffer is not supported."); + } + /* + public void deleteFrameBuffer(FrameBuffer fb) { + if (fb.getId() != -1){ + if (context.boundFBO == fb.getId()){ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + context.boundFBO = 0; + } + + if (fb.getDepthBuffer() != null){ + deleteRenderBuffer(fb, fb.getDepthBuffer()); + } + if (fb.getColorBuffer() != null){ + deleteRenderBuffer(fb, fb.getColorBuffer()); + } + + intBuf1.put(0, fb.getId()); + glDeleteFramebuffersEXT(intBuf1); + fb.resetObject(); + + statistics.onDeleteFrameBuffer(); + } + } + */ + + /*********************************************************************\ + |* Textures *| + \*********************************************************************/ + private int convertTextureType(Texture.Type type) { + switch (type) { + case TwoDimensional: + return GLES20.GL_TEXTURE_2D; + // case TwoDimensionalArray: + // return EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT; +// case ThreeDimensional: + // return GLES20.GL_TEXTURE_3D; + case CubeMap: + return GLES20.GL_TEXTURE_CUBE_MAP; + default: + throw new UnsupportedOperationException("Unknown texture type: " + type); + } + } + + private int convertMagFilter(Texture.MagFilter filter) { + switch (filter) { + case Bilinear: + return GLES20.GL_LINEAR; + case Nearest: + return GLES20.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown mag filter: " + filter); + } + } + + private int convertMinFilter(Texture.MinFilter filter) { + switch (filter) { + case Trilinear: + return GLES20.GL_LINEAR_MIPMAP_LINEAR; + case BilinearNearestMipMap: + return GLES20.GL_LINEAR_MIPMAP_NEAREST; + case NearestLinearMipMap: + return GLES20.GL_NEAREST_MIPMAP_LINEAR; + case NearestNearestMipMap: + return GLES20.GL_NEAREST_MIPMAP_NEAREST; + case BilinearNoMipMaps: + return GLES20.GL_LINEAR; + case NearestNoMipMaps: + return GLES20.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown min filter: " + filter); + } + } + + private int convertWrapMode(Texture.WrapMode mode) { + switch (mode) { +// case BorderClamp: +// return GLES20.GL_CLAMP_TO_BORDER; +// case Clamp: +// return GLES20.GL_CLAMP; + case EdgeClamp: + return GLES20.GL_CLAMP_TO_EDGE; + case Repeat: + return GLES20.GL_REPEAT; + case MirroredRepeat: + return GLES20.GL_MIRRORED_REPEAT; + default: + throw new UnsupportedOperationException("Unknown wrap mode: " + mode); + } + } + + /** + * <code>setupTextureParams</code> sets the OpenGL context texture parameters + * @param tex the Texture to set the texture parameters from + */ + private void setupTextureParams(Texture tex) { + int target = convertTextureType(tex.getType()); + + // filter things + int minFilter = convertMinFilter(tex.getMinFilter()); + int magFilter = convertMagFilter(tex.getMagFilter()); + + if (verboseLogging) { + logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_MIN_FILTER, " + minFilter + ")"); + } + + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MIN_FILTER, minFilter); + + if (verboseLogging) { + logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_MAG_FILTER, " + magFilter + ")"); + } + + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MAG_FILTER, magFilter); + + /* + if (tex.getAnisotropicFilter() > 1){ + + if (GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic){ + glTexParameterf(target, + EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, + tex.getAnisotropicFilter()); + } + + } + */ + // repeat modes + + switch (tex.getType()) { + case ThreeDimensional: + case CubeMap: // cubemaps use 3D coords + // GL_TEXTURE_WRAP_R is not available in api 8 + //GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R))); + case TwoDimensional: + case TwoDimensionalArray: + + if (verboseLogging) { + logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_WRAP_T, " + convertWrapMode(tex.getWrap(WrapAxis.T))); + } + + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); + + // fall down here is intentional.. +// case OneDimensional: + + if (verboseLogging) { + logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_WRAP_S, " + convertWrapMode(tex.getWrap(WrapAxis.S))); + } + + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); + break; + default: + throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); + } + + // R to Texture compare mode +/* + if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off){ + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_MODE, GLES20.GL_COMPARE_R_TO_TEXTURE); + GLES20.glTexParameteri(target, GLES20.GL_DEPTH_TEXTURE_MODE, GLES20.GL_INTENSITY); + if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual){ + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_FUNC, GLES20.GL_GEQUAL); + }else{ + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_FUNC, GLES20.GL_LEQUAL); + } + } + */ + } + + /** + * <code>updateTexImageData</code> activates and binds the texture + * @param img + * @param type + * @param mips + */ + public void updateTexImageData(Image img, Texture.Type type, boolean mips) { + int texId = img.getId(); + if (texId == -1) { + // create texture + if (verboseLogging) { + logger.info("GLES20.glGenTexture(1, buffer)"); + } + + GLES20.glGenTextures(1, intBuf1); + texId = intBuf1.get(0); + img.setId(texId); + objManager.registerForCleanup(img); + + statistics.onNewTexture(); + } + + // bind texture + int target = convertTextureType(type); + if (context.boundTextures[0] != img) { + if (context.boundTextureUnit != 0) { + if (verboseLogging) { + logger.info("GLES20.glActiveTexture(GLES20.GL_TEXTURE0)"); + } + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + context.boundTextureUnit = 0; + } + + if (verboseLogging) { + logger.info("GLES20.glBindTexture(" + target + ", " + texId + ")"); + } + + GLES20.glBindTexture(target, texId); + context.boundTextures[0] = img; + } + + + if (target == GLES20.GL_TEXTURE_CUBE_MAP) { + // Upload a cube map / sky box + @SuppressWarnings("unchecked") + List<AndroidImageInfo> bmps = (List<AndroidImageInfo>) img.getEfficentData(); + if (bmps != null) { + // Native android bitmap + if (bmps.size() != 6) { + throw new UnsupportedOperationException("Invalid texture: " + img + + "Cubemap textures must contain 6 data units."); + } + for (int i = 0; i < 6; i++) { + TextureUtil.uploadTextureBitmap(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, bmps.get(i).getBitmap(), false, powerOf2); + } + } else { + // Standard jme3 image data + List<ByteBuffer> data = img.getData(); + if (data.size() != 6) { + logger.log(Level.WARNING, "Invalid texture: {0}\n" + + "Cubemap textures must contain 6 data units.", img); + return; + } + for (int i = 0; i < 6; i++) { + TextureUtil.uploadTexture(img, GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0, tdc, false, powerOf2); + } + } + } else { + TextureUtil.uploadTexture(img, target, 0, 0, tdc, false, powerOf2); + + if (verboseLogging) { + logger.info("GLES20.glTexParameteri(" + target + "GLES11.GL_GENERATE_MIMAP, GLES20.GL_TRUE)"); + } + + if (!img.hasMipmaps() && mips) { + // No pregenerated mips available, + // generate from base level if required + if (verboseLogging) { + logger.info("GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D)"); + } + GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D); + } + } + + img.clearUpdateNeeded(); + } + + public void setTexture(int unit, Texture tex) { + Image image = tex.getImage(); + if (image.isUpdateNeeded()) { + /* + Bitmap bmp = (Bitmap)image.getEfficentData(); + if (bmp != null) + { + // Check if the bitmap got recycled, can happen after wakeup/restart + if ( bmp.isRecycled() ) + { + // We need to reload the bitmap + Texture textureReloaded = JmeSystem.newAssetManager().loadTexture((TextureKey)tex.getKey()); + image.setEfficentData( textureReloaded.getImage().getEfficentData()); + } + } + */ + updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels()); + } + + int texId = image.getId(); + assert texId != -1; + + if (texId == -1) { + logger.warning("error: texture image has -1 id"); + } + + Image[] textures = context.boundTextures; + + int type = convertTextureType(tex.getType()); + if (!context.textureIndexList.moveToNew(unit)) { +// if (context.boundTextureUnit != unit){ +// glActiveTexture(GL_TEXTURE0 + unit); +// context.boundTextureUnit = unit; +// } +// glEnable(type); + } + + if (textures[unit] != image) { + if (context.boundTextureUnit != unit) { + if (verboseLogging) { + logger.info("GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + " + unit + ")"); + } + GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + + if (verboseLogging) { + logger.info("GLES20.glBindTexture(" + type + ", " + texId + ")"); + } + + GLES20.glBindTexture(type, texId); + textures[unit] = image; + + statistics.onTextureUse(tex.getImage(), true); + } else { + statistics.onTextureUse(tex.getImage(), false); + } + + setupTextureParams(tex); + } + + public void clearTextureUnits() { + IDList textureList = context.textureIndexList; + Image[] textures = context.boundTextures; + for (int i = 0; i < textureList.oldLen; i++) { + int idx = textureList.oldList[i]; +// if (context.boundTextureUnit != idx){ +// glActiveTexture(GL_TEXTURE0 + idx); +// context.boundTextureUnit = idx; +// } +// glDisable(convertTextureType(textures[idx].getType())); + textures[idx] = null; + } + context.textureIndexList.copyNewToOld(); + } + + public void deleteImage(Image image) { + int texId = image.getId(); + if (texId != -1) { + intBuf1.put(0, texId); + intBuf1.position(0).limit(1); + + if (verboseLogging) { + logger.info("GLES20.glDeleteTexture(1, buffer)"); + } + + GLES20.glDeleteTextures(1, intBuf1); + image.resetObject(); + + statistics.onDeleteTexture(); + } + } + + /*********************************************************************\ + |* Vertex Buffers and Attributes *| + \*********************************************************************/ + private int convertUsage(Usage usage) { + switch (usage) { + case Static: + return GLES20.GL_STATIC_DRAW; + case Dynamic: + return GLES20.GL_DYNAMIC_DRAW; + case Stream: + return GLES20.GL_STREAM_DRAW; + default: + throw new RuntimeException("Unknown usage type."); + } + } + + private int convertFormat(Format format) { + switch (format) { + case Byte: + return GLES20.GL_BYTE; + case UnsignedByte: + return GLES20.GL_UNSIGNED_BYTE; + case Short: + return GLES20.GL_SHORT; + case UnsignedShort: + return GLES20.GL_UNSIGNED_SHORT; + case Int: + return GLES20.GL_INT; + case UnsignedInt: + return GLES20.GL_UNSIGNED_INT; + /* + case Half: + return NVHalfFloat.GL_HALF_FLOAT_NV; + // return ARBHalfFloatVertex.GL_HALF_FLOAT; + */ + case Float: + return GLES20.GL_FLOAT; +// case Double: +// return GLES20.GL_DOUBLE; + default: + throw new RuntimeException("Unknown buffer format."); + + } + } + + public void updateBufferData(VertexBuffer vb) { + + if (verboseLogging) { + logger.info("updateBufferData(" + vb + ")"); + } + + int bufId = vb.getId(); + boolean created = false; + if (bufId == -1) { + // create buffer + + if (verboseLogging) { + logger.info("GLES20.glGenBuffers(" + 1 + ", buffer)"); + } + + GLES20.glGenBuffers(1, intBuf1); + bufId = intBuf1.get(0); + vb.setId(bufId); + objManager.registerForCleanup(vb); + + created = true; + } + + // bind buffer + int target; + if (vb.getBufferType() == VertexBuffer.Type.Index) { + target = GLES20.GL_ELEMENT_ARRAY_BUFFER; + + if (verboseLogging) { + logger.info("vb.getBufferType() == VertexBuffer.Type.Index"); + } + + if (context.boundElementArrayVBO != bufId) { + + if (verboseLogging) { + logger.info("GLES20.glBindBuffer(" + target + ", " + bufId + ")"); + } + + GLES20.glBindBuffer(target, bufId); + context.boundElementArrayVBO = bufId; + } + } else { + if (verboseLogging) { + logger.info("vb.getBufferType() != VertexBuffer.Type.Index"); + } + + target = GLES20.GL_ARRAY_BUFFER; + + if (context.boundArrayVBO != bufId) { + + if (verboseLogging) { + logger.info("GLES20.glBindBuffer(" + target + ", " + bufId + ")"); + } + + GLES20.glBindBuffer(target, bufId); + context.boundArrayVBO = bufId; + } + } + + int usage = convertUsage(vb.getUsage()); + vb.getData().clear(); + + if (created || vb.hasDataSizeChanged()) { + // upload data based on format + int size = vb.getData().capacity() * vb.getFormat().getComponentSize(); + + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + + if (verboseLogging) { + logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")"); + } + + GLES20.glBufferData(target, size, (ByteBuffer) vb.getData(), usage); + break; + // case Half: + case Short: + case UnsignedShort: + + if (verboseLogging) { + logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")"); + } + + GLES20.glBufferData(target, size, (ShortBuffer) vb.getData(), usage); + break; + case Int: + case UnsignedInt: + + if (verboseLogging) { + logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")"); + } + + GLES20.glBufferData(target, size, (IntBuffer) vb.getData(), usage); + break; + case Float: + if (verboseLogging) { + logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")"); + } + + GLES20.glBufferData(target, size, (FloatBuffer) vb.getData(), usage); + break; + case Double: + if (verboseLogging) { + logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")"); + } + + GLES20.glBufferData(target, size, (DoubleBuffer) vb.getData(), usage); + break; + default: + throw new RuntimeException("Unknown buffer format."); + } + } else { + int size = vb.getData().capacity() * vb.getFormat().getComponentSize(); + + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + if (verboseLogging) { + logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))"); + } + + GLES20.glBufferSubData(target, 0, size, (ByteBuffer) vb.getData()); + break; + case Short: + case UnsignedShort: + if (verboseLogging) { + logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))"); + } + + GLES20.glBufferSubData(target, 0, size, (ShortBuffer) vb.getData()); + break; + case Int: + case UnsignedInt: + if (verboseLogging) { + logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))"); + } + + GLES20.glBufferSubData(target, 0, size, (IntBuffer) vb.getData()); + break; + case Float: + if (verboseLogging) { + logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))"); + } + + GLES20.glBufferSubData(target, 0, size, (FloatBuffer) vb.getData()); + break; + case Double: + if (verboseLogging) { + logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))"); + } + + GLES20.glBufferSubData(target, 0, size, (DoubleBuffer) vb.getData()); + break; + default: + throw new RuntimeException("Unknown buffer format."); + } + } +// }else{ +// if (created || vb.hasDataSizeChanged()){ +// glBufferData(target, vb.getData().capacity() * vb.getFormat().getComponentSize(), usage); +// } +// +// ByteBuffer buf = glMapBuffer(target, +// GL_WRITE_ONLY, +// vb.getMappedData()); +// +// if (buf != vb.getMappedData()){ +// buf = buf.order(ByteOrder.nativeOrder()); +// vb.setMappedData(buf); +// } +// +// buf.clear(); +// +// switch (vb.getFormat()){ +// case Byte: +// case UnsignedByte: +// buf.put( (ByteBuffer) vb.getData() ); +// break; +// case Short: +// case UnsignedShort: +// buf.asShortBuffer().put( (ShortBuffer) vb.getData() ); +// break; +// case Int: +// case UnsignedInt: +// buf.asIntBuffer().put( (IntBuffer) vb.getData() ); +// break; +// case Float: +// buf.asFloatBuffer().put( (FloatBuffer) vb.getData() ); +// break; +// case Double: +// break; +// default: +// throw new RuntimeException("Unknown buffer format."); +// } +// +// glUnmapBuffer(target); +// } + + vb.clearUpdateNeeded(); + } + + public void deleteBuffer(VertexBuffer vb) { + int bufId = vb.getId(); + if (bufId != -1) { + // delete buffer + intBuf1.put(0, bufId); + intBuf1.position(0).limit(1); + if (verboseLogging) { + logger.info("GLES20.glDeleteBuffers(1, buffer)"); + } + + GLES20.glDeleteBuffers(1, intBuf1); + vb.resetObject(); + } + } + + public void clearVertexAttribs() { + IDList attribList = context.attribIndexList; + for (int i = 0; i < attribList.oldLen; i++) { + int idx = attribList.oldList[i]; + + if (verboseLogging) { + logger.info("GLES20.glDisableVertexAttribArray(" + idx + ")"); + } + + GLES20.glDisableVertexAttribArray(idx); + context.boundAttribs[idx] = null; + } + context.attribIndexList.copyNewToOld(); + } + + public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { + if (verboseLogging) { + logger.info("setVertexAttrib(" + vb + ", " + idb + ")"); + } + + if (vb.getBufferType() == VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); + } + + if (vb.isUpdateNeeded() && idb == null) { + updateBufferData(vb); + } + + int programId = context.boundShaderProgram; + if (programId > 0) { + Attribute attrib = boundShader.getAttribute(vb.getBufferType()); + int loc = attrib.getLocation(); + if (loc == -1) { + + if (verboseLogging) { + logger.warning("location is invalid for attrib: [" + vb.getBufferType().name() + "]"); + } + + return; // not defined + } + + if (loc == -2) { +// stringBuf.setLength(0); +// stringBuf.append("in").append(vb.getBufferType().name()).append('\0'); +// updateNameBuffer(); + + String attributeName = "in" + vb.getBufferType().name(); + + if (verboseLogging) { + logger.info("GLES20.glGetAttribLocation(" + programId + ", " + attributeName + ")"); + } + + loc = GLES20.glGetAttribLocation(programId, attributeName); + + // not really the name of it in the shader (inPosition\0) but + // the internal name of the enum (Position). + if (loc < 0) { + attrib.setLocation(-1); + + if (verboseLogging) { + logger.warning("attribute is invalid in shader: [" + vb.getBufferType().name() + "]"); + } + + return; // not available in shader. + } else { + attrib.setLocation(loc); + } + } + + VertexBuffer[] attribs = context.boundAttribs; + if (!context.attribIndexList.moveToNew(loc)) { + if (verboseLogging) { + logger.info("GLES20.glEnableVertexAttribArray(" + loc + ")"); + } + + GLES20.glEnableVertexAttribArray(loc); + //System.out.println("Enabled ATTRIB IDX: "+loc); + } + if (attribs[loc] != vb) { + // NOTE: Use id from interleaved buffer if specified + int bufId = idb != null ? idb.getId() : vb.getId(); + assert bufId != -1; + + if (bufId == -1) { + logger.warning("invalid buffer id"); + } + + if (context.boundArrayVBO != bufId) { + if (verboseLogging) { + logger.info("GLES20.glBindBuffer(" + GLES20.GL_ARRAY_BUFFER + ", " + bufId + ")"); + } + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufId); + context.boundArrayVBO = bufId; + } + + vb.getData().clear(); + + if (verboseLogging) { + logger.info("GLES20.glVertexAttribPointer(" + + "location=" + loc + ", " + + "numComponents=" + vb.getNumComponents() + ", " + + "format=" + vb.getFormat() + ", " + + "isNormalized=" + vb.isNormalized() + ", " + + "stride=" + vb.getStride() + ", " + + "data.capacity=" + vb.getData().capacity() + ")"); + } + + Android22Workaround.glVertexAttribPointer(loc, + vb.getNumComponents(), + convertFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + 0); + + attribs[loc] = vb; + } + } else { + throw new IllegalStateException("Cannot render mesh without shader bound"); + } + } + + public void setVertexAttrib(VertexBuffer vb) { + setVertexAttrib(vb, null); + } + + public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) { + /* if (count > 1){ + ARBDrawInstanced.glDrawArraysInstancedARB(convertElementMode(mode), 0, + vertCount, count); + }else{*/ + if (verboseLogging) { + logger.info("GLES20.glDrawArrays(" + vertCount + ")"); + } + + GLES20.glDrawArrays(convertElementMode(mode), 0, vertCount); + /* + }*/ + } + + public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) { + + if (verboseLogging) { + logger.info("drawTriangleList(" + count + ")"); + } + + if (indexBuf.getBufferType() != VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Only index buffers are allowed as triangle lists."); + } + + if (indexBuf.isUpdateNeeded()) { + if (verboseLogging) { + logger.info("updateBufferData for indexBuf."); + } + updateBufferData(indexBuf); + } + + int bufId = indexBuf.getId(); + assert bufId != -1; + + if (bufId == -1) { + logger.info("invalid buffer id!"); + } + + if (context.boundElementArrayVBO != bufId) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, {0})", bufId); + } + + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufId); + context.boundElementArrayVBO = bufId; + } + + int vertCount = mesh.getVertexCount(); + boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); + + Buffer indexData = indexBuf.getData(); + + 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.TriangleStrip); + } + int elementLength = elementLengths[i]; + + if (useInstancing) { + //ARBDrawInstanced. + throw new IllegalArgumentException("instancing is not supported."); + /* + GLES20.glDrawElementsInstancedARB(elMode, + elementLength, + fmt, + curOffset, + count); + */ + } else { + indexBuf.getData().position(curOffset); + if (verboseLogging) { + logger.log(Level.INFO, "glDrawElements(): {0}, {1}", new Object[]{elementLength, curOffset}); + } + + GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); + /* + glDrawRangeElements(elMode, + 0, + vertCount, + elementLength, + fmt, + curOffset); + */ + } + + curOffset += elementLength * elSize; + } + } else { + if (useInstancing) { + throw new IllegalArgumentException("instancing is not supported."); + //ARBDrawInstanced. +/* + GLES20.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()), + indexBuf.getData().capacity(), + convertFormat(indexBuf.getFormat()), + 0, + count); + */ + } else { + indexData.clear(); + + if (verboseLogging) { + logger.log(Level.INFO, "glDrawElements(), indexBuf.capacity ({0}), vertCount ({1})", new Object[]{indexBuf.getData().capacity(), vertCount}); + } + + GLES11.glDrawElements( + convertElementMode(mesh.getMode()), + indexBuf.getData().capacity(), + convertFormat(indexBuf.getFormat()), + 0); + } + } + } + + /*********************************************************************\ + |* Render Calls *| + \*********************************************************************/ + public int convertElementMode(Mesh.Mode mode) { + switch (mode) { + case Points: + return GLES20.GL_POINTS; + case Lines: + return GLES20.GL_LINES; + case LineLoop: + return GLES20.GL_LINE_LOOP; + case LineStrip: + return GLES20.GL_LINE_STRIP; + case Triangles: + return GLES20.GL_TRIANGLES; + case TriangleFan: + return GLES20.GL_TRIANGLE_FAN; + case TriangleStrip: + return GLES20.GL_TRIANGLE_STRIP; + default: + throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); + } + } + + public void updateVertexArray(Mesh mesh) { + logger.log(Level.INFO, "updateVertexArray({0})", mesh); + int id = mesh.getId(); + /* + if (id == -1){ + IntBuffer temp = intBuf1; + // ARBVertexArrayObject.glGenVertexArrays(temp); + GLES20.glGenVertexArrays(temp); + id = temp.get(0); + mesh.setId(id); + } + + if (context.boundVertexArray != id){ + // ARBVertexArrayObject.glBindVertexArray(id); + GLES20.glBindVertexArray(id); + context.boundVertexArray = id; + } + */ + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + + + 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 (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + } + + /** + * renderMeshVertexArray renders a mesh using vertex arrays + * @param mesh + * @param lod + * @param count + */ + private void renderMeshVertexArray(Mesh mesh, int lod, int count) { + if (verboseLogging) { + logger.info("renderMeshVertexArray"); + } + + // IntMap<VertexBuffer> buffers = mesh.getBuffers(); + 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 (vb.getStride() == 0) { + // not interleaved + setVertexAttrib_Array(vb); + } else { + // interleaved + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + setVertexAttrib_Array(vb, interleavedData); + } + } + + VertexBuffer indices = null; + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index);//buffers.get(Type.Index.ordinal()); + } + if (indices != null) { + drawTriangleList_Array(indices, mesh, count); + } else { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glDrawArrays({0}, {1}, {2})", + new Object[]{mesh.getMode(), 0, mesh.getVertexCount()}); + } + + GLES20.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + private void renderMeshDefault(Mesh mesh, int lod, int count) { + if (verboseLogging) { + logger.log(Level.INFO, "renderMeshDefault({0}, {1}, {2})", + new Object[]{mesh, lod, count}); + } + VertexBuffer indices = null; + + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + + //IntMap<VertexBuffer> buffers = mesh.getBuffers(); ; + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index);// buffers.get(Type.Index.ordinal()); + } + 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 (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { +// throw new UnsupportedOperationException("Cannot render without index buffer"); + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glDrawArrays({0}, 0, {1})", + new Object[]{convertElementMode(mesh.getMode()), mesh.getVertexCount()}); + } + + GLES20.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + public void renderMesh(Mesh mesh, int lod, int count) { + if (context.pointSize != mesh.getPointSize()) { + + if (verboseLogging) { + logger.log(Level.INFO, "GLES10.glPointSize({0})", mesh.getPointSize()); + } + + GLES10.glPointSize(mesh.getPointSize()); + context.pointSize = mesh.getPointSize(); + } + if (context.lineWidth != mesh.getLineWidth()) { + + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glLineWidth({0})", mesh.getLineWidth()); + } + + GLES20.glLineWidth(mesh.getLineWidth()); + context.lineWidth = mesh.getLineWidth(); + } + + statistics.onMeshDrawn(mesh, lod); +// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){ +// renderMeshVertexArray(mesh, lod, count); +// }else{ + + if (useVBO) { + if (verboseLogging) { + logger.info("RENDERING A MESH USING VertexBufferObject"); + } + + renderMeshDefault(mesh, lod, count); + } else { + if (verboseLogging) { + logger.info("RENDERING A MESH USING VertexArray"); + } + + renderMeshVertexArray(mesh, lod, count); + } + +// } + } + + /** + * drawTriangleList_Array uses Vertex Array + * @param indexBuf + * @param mesh + * @param count + */ + public void drawTriangleList_Array(VertexBuffer indexBuf, Mesh mesh, int count) { + if (verboseLogging) { + logger.log(Level.INFO, "drawTriangleList_Array(Count = {0})", count); + } + + if (indexBuf.getBufferType() != VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Only index buffers are allowed as triangle lists."); + } + + boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); + if (useInstancing) { + throw new IllegalArgumentException("Caps.MeshInstancing is not supported."); + } + + int vertCount = mesh.getVertexCount(); + Buffer indexData = indexBuf.getData(); + indexData.clear(); + + 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.TriangleStrip); + } + int elementLength = elementLengths[i]; + + indexBuf.getData().position(curOffset); + if (verboseLogging) { + logger.log(Level.INFO, "glDrawElements(): {0}, {1}", new Object[]{elementLength, curOffset}); + } + + GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); + + curOffset += elementLength * elSize; + } + } else { + if (verboseLogging) { + logger.log(Level.INFO, "glDrawElements(), indexBuf.capacity ({0}), vertCount ({1})", new Object[]{indexBuf.getData().capacity(), vertCount}); + } + + GLES20.glDrawElements( + convertElementMode(mesh.getMode()), + indexBuf.getData().capacity(), + convertFormat(indexBuf.getFormat()), + indexBuf.getData()); + } + } + + /** + * setVertexAttrib_Array uses Vertex Array + * @param vb + * @param idb + */ + public void setVertexAttrib_Array(VertexBuffer vb, VertexBuffer idb) { + if (verboseLogging) { + logger.log(Level.INFO, "setVertexAttrib_Array({0}, {1})", new Object[]{vb, idb}); + } + + if (vb.getBufferType() == VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); + } + + // Get shader + int programId = context.boundShaderProgram; + if (programId > 0) { + VertexBuffer[] attribs = context.boundAttribs; + + Attribute attrib = boundShader.getAttribute(vb.getBufferType()); + int loc = attrib.getLocation(); + if (loc == -1) { + //throw new IllegalArgumentException("Location is invalid for attrib: [" + vb.getBufferType().name() + "]"); + if (verboseLogging) { + logger.log(Level.WARNING, "attribute is invalid in shader: [{0}]", vb.getBufferType().name()); + } + return; + } else if (loc == -2) { + String attributeName = "in" + vb.getBufferType().name(); + + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glGetAttribLocation({0}, {1})", new Object[]{programId, attributeName}); + } + + loc = GLES20.glGetAttribLocation(programId, attributeName); + if (loc < 0) { + attrib.setLocation(-1); + if (verboseLogging) { + logger.log(Level.WARNING, "attribute is invalid in shader: [{0}]", vb.getBufferType().name()); + } + return; // not available in shader. + } else { + attrib.setLocation(loc); + } + + } // if (loc == -2) + + if ((attribs[loc] != vb) || vb.isUpdateNeeded()) { + // NOTE: Use data from interleaved buffer if specified + VertexBuffer avb = idb != null ? idb : vb; + avb.getData().clear(); + avb.getData().position(vb.getOffset()); + + if (verboseLogging) { + logger.log(Level.INFO, + "GLES20.glVertexAttribPointer(" + + "location={0}, " + + "numComponents={1}, " + + "format={2}, " + + "isNormalized={3}, " + + "stride={4}, " + + "data.capacity={5})", + new Object[]{loc, vb.getNumComponents(), + vb.getFormat(), + vb.isNormalized(), + vb.getStride(), + avb.getData().capacity()}); + } + + + // Upload attribute data + GLES20.glVertexAttribPointer(loc, + vb.getNumComponents(), + convertFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + avb.getData()); + checkGLError(); + + GLES20.glEnableVertexAttribArray(loc); + + attribs[loc] = vb; + } // if (attribs[loc] != vb) + } else { + throw new IllegalStateException("Cannot render mesh without shader bound"); + } + } + + /** + * setVertexAttrib_Array uses Vertex Array + * @param vb + */ + public void setVertexAttrib_Array(VertexBuffer vb) { + setVertexAttrib_Array(vb, null); + } + + public void setAlphaToCoverage(boolean value) { + if (value) { + GLES20.glEnable(GLES20.GL_SAMPLE_ALPHA_TO_COVERAGE); + } else { + GLES20.glDisable(GLES20.GL_SAMPLE_ALPHA_TO_COVERAGE); + } + } + + @Override + public void invalidateState() { + context.reset(); + boundShader = null; + lastFb = null; + } +} diff --git a/engine/src/android/com/jme3/renderer/android/TextureUtil.java b/engine/src/android/com/jme3/renderer/android/TextureUtil.java new file mode 100644 index 0000000..53b96b4 --- /dev/null +++ b/engine/src/android/com/jme3/renderer/android/TextureUtil.java @@ -0,0 +1,297 @@ +package com.jme3.renderer.android; + +import android.graphics.Bitmap; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import com.jme3.asset.AndroidImageInfo; +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import java.nio.ByteBuffer; +import javax.microedition.khronos.opengles.GL10; + +public class TextureUtil { + + public static int convertTextureFormat(Format fmt){ + switch (fmt){ + case Alpha16: + case Alpha8: + return GL10.GL_ALPHA; + case Luminance8Alpha8: + case Luminance16Alpha16: + return GL10.GL_LUMINANCE_ALPHA; + case Luminance8: + case Luminance16: + return GL10.GL_LUMINANCE; + case RGB10: + case RGB16: + case BGR8: + case RGB8: + case RGB565: + return GL10.GL_RGB; + case RGB5A1: + case RGBA16: + case RGBA8: + return GL10.GL_RGBA; + + case Depth: + return GLES20.GL_DEPTH_COMPONENT; + case Depth16: + return GLES20.GL_DEPTH_COMPONENT16; + case Depth24: + case Depth32: + case Depth32F: + throw new UnsupportedOperationException("Unsupported depth format: " + fmt); + + case DXT1A: + throw new UnsupportedOperationException("Unsupported format: " + fmt); + default: + throw new UnsupportedOperationException("Unrecognized format: " + fmt); + } + } + + private static void buildMipmap(Bitmap bitmap) { + int level = 0; + int height = bitmap.getHeight(); + int width = bitmap.getWidth(); + + while (height >= 1 || width >= 1) { + //First of all, generate the texture from our bitmap and set it to the according level + GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bitmap, 0); + + if (height == 1 || width == 1) { + break; + } + + //Increase the mipmap level + level++; + + height /= 2; + width /= 2; + Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true); + + bitmap.recycle(); + bitmap = bitmap2; + } + } + + /** + * <code>uploadTextureBitmap</code> uploads a native android bitmap + * @param target + * @param bitmap + * @param generateMips + * @param powerOf2 + */ + public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean generateMips, boolean powerOf2) + { + if (!powerOf2) + { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(height)) + { + // scale to power of two + width = FastMath.nearestPowerOfTwo(width); + height = FastMath.nearestPowerOfTwo(height); + Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true); + bitmap.recycle(); + bitmap = bitmap2; + } + } + + if (generateMips) + { + buildMipmap(bitmap); + } + else + { + GLUtils.texImage2D(target, 0, bitmap, 0); + //bitmap.recycle(); + } + } + + public static void uploadTexture( + Image img, + int target, + int index, + int border, + boolean tdc, + boolean generateMips, + boolean powerOf2){ + + if (img.getEfficentData() instanceof AndroidImageInfo){ + // If image was loaded from asset manager, use fast path + AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData(); + uploadTextureBitmap(target, imageInfo.getBitmap(), generateMips, powerOf2); + return; + } + + // Otherwise upload image directly. + // Prefer to only use power of 2 textures here to avoid errors. + + Image.Format fmt = img.getFormat(); + ByteBuffer data; + if (index >= 0 || img.getData() != null && img.getData().size() > 0){ + data = img.getData(index); + }else{ + data = null; + } + + int width = img.getWidth(); + int height = img.getHeight(); + int depth = img.getDepth(); + + boolean compress = false; + int internalFormat = -1; + int format = -1; + int dataType = -1; + + switch (fmt){ + case Alpha16: + case Alpha8: + format = GLES20.GL_ALPHA; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Luminance8: + format = GLES20.GL_LUMINANCE; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Luminance8Alpha8: + format = GLES20.GL_LUMINANCE_ALPHA; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Luminance16Alpha16: + format = GLES20.GL_LUMINANCE_ALPHA; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Luminance16: + format = GLES20.GL_LUMINANCE; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case RGB565: + format = GLES20.GL_RGB; + internalFormat = GLES20.GL_RGB565; + dataType = GLES20.GL_UNSIGNED_SHORT_5_6_5; + break; + case ARGB4444: + format = GLES20.GL_RGBA; + dataType = GLES20.GL_UNSIGNED_SHORT_4_4_4_4; + break; + case RGB10: + format = GLES20.GL_RGB; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case RGB16: + format = GLES20.GL_RGB; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case RGB5A1: + format = GLES20.GL_RGBA; + internalFormat = GLES20.GL_RGB5_A1; + dataType = GLES20.GL_UNSIGNED_SHORT_5_5_5_1; + break; + case RGB8: + format = GLES20.GL_RGB; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case BGR8: + format = GLES20.GL_RGB; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case RGBA16: + format = GLES20.GL_RGBA; + internalFormat = GLES20.GL_RGBA4; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case RGBA8: + format = GLES20.GL_RGBA; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case DXT1A: + format = GLES20.GL_COMPRESSED_TEXTURE_FORMATS; + dataType = GLES20.GL_UNSIGNED_BYTE; + case Depth: + format = GLES20.GL_DEPTH_COMPONENT; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Depth16: + format = GLES20.GL_DEPTH_COMPONENT; + internalFormat = GLES20.GL_DEPTH_COMPONENT16; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Depth24: + case Depth32: + case Depth32F: + throw new UnsupportedOperationException("Unsupported depth format: " + fmt); + default: + throw new UnsupportedOperationException("Unrecognized format: " + fmt); + } + + if (internalFormat == -1) + { + internalFormat = format; + } + + if (data != null) + GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); + + int[] mipSizes = img.getMipMapSizes(); + int pos = 0; + if (mipSizes == null){ + if (data != null) + mipSizes = new int[]{ data.capacity() }; + else + mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 }; + } + + // XXX: might want to change that when support + // of more than paletted compressions is added.. + if (compress){ + data.clear(); + GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D, + 1 - mipSizes.length, + format, + width, + height, + 0, + data.capacity(), + data); + return; + } + + for (int i = 0; i < mipSizes.length; i++){ + int mipWidth = Math.max(1, width >> i); + int mipHeight = Math.max(1, height >> i); + int mipDepth = Math.max(1, depth >> i); + + if (data != null){ + data.position(pos); + data.limit(pos + mipSizes[i]); + } + + if (compress && data != null){ + GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D, + i, + format, + mipWidth, + mipHeight, + 0, + data.remaining(), + data); + }else{ + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, + i, + internalFormat, + mipWidth, + mipHeight, + 0, + format, + dataType, + data); + } + + pos += mipSizes[i]; + } + } + +} |