From 5afe5b35c103c549337a6342d5700a2fa915e1b6 Mon Sep 17 00:00:00 2001 From: Kostya Date: Sun, 9 Feb 2014 15:28:47 +0000 Subject: [PATCH] Added iOS java rendering classes git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@11034 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../renderer/ios/IGLESShaderRenderer.java | 2571 +++++++++++++++++ .../ios/com/jme3/renderer/ios/JmeIosGLES.java | 273 ++ .../com/jme3/renderer/ios/TextureUtil.java | 591 ++++ 3 files changed, 3435 insertions(+) create mode 100644 engine/src/ios/com/jme3/renderer/ios/IGLESShaderRenderer.java create mode 100644 engine/src/ios/com/jme3/renderer/ios/JmeIosGLES.java create mode 100644 engine/src/ios/com/jme3/renderer/ios/TextureUtil.java diff --git a/engine/src/ios/com/jme3/renderer/ios/IGLESShaderRenderer.java b/engine/src/ios/com/jme3/renderer/ios/IGLESShaderRenderer.java new file mode 100644 index 000000000..956e15269 --- /dev/null +++ b/engine/src/ios/com/jme3/renderer/ios/IGLESShaderRenderer.java @@ -0,0 +1,2571 @@ +package com.jme3.renderer.ios; + +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 java.nio.*; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3tools.shader.ShaderDebug; + +/** + * The Renderer is responsible for taking rendering commands and + * executing them on the underlying video hardware. + * + * @author Kirill Vainer + */ +public class IGLESShaderRenderer implements Renderer { + + private static final Logger logger = Logger.getLogger(IGLESShaderRenderer.class.getName()); + private static final boolean VALIDATE_SHADER = false; + + private final NativeObjectManager objManager = new NativeObjectManager(); + private final EnumSet caps = EnumSet.noneOf(Caps.class); + private final Statistics statistics = new Statistics(); + private final StringBuilder stringBuf = new StringBuilder(250); + private final RenderContext context = new RenderContext(); + private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); + + private final int maxFBOAttachs = 1; // Only 1 color attachment on ES + private final int maxMRTFBOAttachs = 1; // FIXME for now, not sure if > 1 is needed for ES + + private final int[] intBuf1 = new int[1]; + private final int[] intBuf16 = new int[16]; + + private int glslVer; + private int vertexTextureUnits; + private int fragTextureUnits; + private int vertexUniforms; + private int fragUniforms; + private int vertexAttribs; + private int maxRBSize; + private int maxTexSize; + private int maxCubeTexSize; + + private FrameBuffer lastFb = null; + private FrameBuffer mainFbOverride = null; + private boolean useVBO = false; + private boolean powerVr = false; + + private Shader boundShader; + + private int vpX, vpY, vpW, vpH; + private int clipX, clipY, clipW, clipH; + + public IGLESShaderRenderer() { + logger.log(Level.FINE, "IGLESShaderRenderer Constructor"); + } + + /** + * Get the capabilities of the renderer. + * @return The capabilities of the renderer. + */ + public EnumSet getCaps() { + logger.log(Level.FINE, "IGLESShaderRenderer getCaps"); + return caps; + } + + /** + * The statistics allow tracking of how data + * per frame, such as number of objects rendered, number of triangles, etc. + * These are updated when the Renderer's methods are used, make sure + * to call {@link Statistics#clearFrame() } at the appropriate time + * to get accurate info per frame. + */ + public Statistics getStatistics() { + logger.log(Level.FINE, "IGLESShaderRenderer getStatistics"); + return statistics; + } + + /** + * Invalidates the current rendering state. Should be called after + * the GL state was changed manually or through an external library. + */ + public void invalidateState() { + logger.log(Level.FINE, "IGLESShaderRenderer invalidateState"); + } + + /** + * Clears certain channels of the currently bound framebuffer. + * + * @param color True if to clear colors (RGBA) + * @param depth True if to clear depth/z + * @param stencil True if to clear stencil buffer (if available, otherwise + * ignored) + */ + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + logger.log(Level.FINE, "IGLESShaderRenderer clearBuffers"); + int bits = 0; + if (color) { + bits = JmeIosGLES.GL_COLOR_BUFFER_BIT; + } + if (depth) { + bits |= JmeIosGLES.GL_DEPTH_BUFFER_BIT; + } + if (stencil) { + bits |= JmeIosGLES.GL_STENCIL_BUFFER_BIT; + } + if (bits != 0) { + JmeIosGLES.glClear(bits); + JmeIosGLES.checkGLError(); + } + } + + /** + * Sets the background (aka clear) color. + * + * @param color The background color to set + */ + public void setBackgroundColor(ColorRGBA color) { + logger.log(Level.FINE, "IGLESShaderRenderer setBackgroundColor"); + JmeIosGLES.glClearColor(color.r, color.g, color.b, color.a); + JmeIosGLES.checkGLError(); + } + + /** + * Applies the given {@link RenderState}, making the necessary + * GL calls so that the state is applied. + */ + public void applyRenderState(RenderState state) { + logger.log(Level.FINE, "IGLESShaderRenderer applyRenderState"); + /* + 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) { + JmeIosGLES.glEnable(JmeIosGLES.GL_DEPTH_TEST); + JmeIosGLES.glDepthFunc(convertTestFunction(context.depthFunc)); + JmeIosGLES.checkGLError(); + context.depthTestEnabled = true; + } else if (!state.isDepthTest() && context.depthTestEnabled) { + JmeIosGLES.glDisable(JmeIosGLES.GL_DEPTH_TEST); + JmeIosGLES.checkGLError(); + context.depthTestEnabled = false; + } + if (state.getDepthFunc() != context.depthFunc) { + JmeIosGLES.glDepthFunc(convertTestFunction(state.getDepthFunc())); + context.depthFunc = state.getDepthFunc(); + } + + if (state.isDepthWrite() && !context.depthWriteEnabled) { + JmeIosGLES.glDepthMask(true); + JmeIosGLES.checkGLError(); + context.depthWriteEnabled = true; + } else if (!state.isDepthWrite() && context.depthWriteEnabled) { + JmeIosGLES.glDepthMask(false); + JmeIosGLES.checkGLError(); + context.depthWriteEnabled = false; + } + if (state.isColorWrite() && !context.colorWriteEnabled) { + JmeIosGLES.glColorMask(true, true, true, true); + JmeIosGLES.checkGLError(); + context.colorWriteEnabled = true; + } else if (!state.isColorWrite() && context.colorWriteEnabled) { + JmeIosGLES.glColorMask(false, false, false, false); + JmeIosGLES.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) { + JmeIosGLES.glEnable(JmeIosGLES.GL_POLYGON_OFFSET_FILL); + JmeIosGLES.glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + JmeIosGLES.checkGLError(); + + context.polyOffsetEnabled = true; + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } else { + if (state.getPolyOffsetFactor() != context.polyOffsetFactor + || state.getPolyOffsetUnits() != context.polyOffsetUnits) { + JmeIosGLES.glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + JmeIosGLES.checkGLError(); + + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } + } + } else { + if (context.polyOffsetEnabled) { + JmeIosGLES.glDisable(JmeIosGLES.GL_POLYGON_OFFSET_FILL); + JmeIosGLES.checkGLError(); + + context.polyOffsetEnabled = false; + context.polyOffsetFactor = 0; + context.polyOffsetUnits = 0; + } + } + if (state.getFaceCullMode() != context.cullMode) { + if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { + JmeIosGLES.glDisable(JmeIosGLES.GL_CULL_FACE); + JmeIosGLES.checkGLError(); + } else { + JmeIosGLES.glEnable(JmeIosGLES.GL_CULL_FACE); + JmeIosGLES.checkGLError(); + } + + switch (state.getFaceCullMode()) { + case Off: + break; + case Back: + JmeIosGLES.glCullFace(JmeIosGLES.GL_BACK); + JmeIosGLES.checkGLError(); + break; + case Front: + JmeIosGLES.glCullFace(JmeIosGLES.GL_FRONT); + JmeIosGLES.checkGLError(); + break; + case FrontAndBack: + JmeIosGLES.glCullFace(JmeIosGLES.GL_FRONT_AND_BACK); + JmeIosGLES.checkGLError(); + break; + default: + throw new UnsupportedOperationException("Unrecognized face cull mode: " + + state.getFaceCullMode()); + } + + context.cullMode = state.getFaceCullMode(); + } + + if (state.getBlendMode() != context.blendMode) { + if (state.getBlendMode() == RenderState.BlendMode.Off) { + JmeIosGLES.glDisable(JmeIosGLES.GL_BLEND); + JmeIosGLES.checkGLError(); + } else { + JmeIosGLES.glEnable(JmeIosGLES.GL_BLEND); + switch (state.getBlendMode()) { + case Off: + break; + case Additive: + JmeIosGLES.glBlendFunc(JmeIosGLES.GL_ONE, JmeIosGLES.GL_ONE); + break; + case AlphaAdditive: + JmeIosGLES.glBlendFunc(JmeIosGLES.GL_SRC_ALPHA, JmeIosGLES.GL_ONE); + break; + case Color: + JmeIosGLES.glBlendFunc(JmeIosGLES.GL_ONE, JmeIosGLES.GL_ONE_MINUS_SRC_COLOR); + break; + case Alpha: + JmeIosGLES.glBlendFunc(JmeIosGLES.GL_SRC_ALPHA, JmeIosGLES.GL_ONE_MINUS_SRC_ALPHA); + break; + case PremultAlpha: + JmeIosGLES.glBlendFunc(JmeIosGLES.GL_ONE, JmeIosGLES.GL_ONE_MINUS_SRC_ALPHA); + break; + case Modulate: + JmeIosGLES.glBlendFunc(JmeIosGLES.GL_DST_COLOR, JmeIosGLES.GL_ZERO); + break; + case ModulateX2: + JmeIosGLES.glBlendFunc(JmeIosGLES.GL_DST_COLOR, JmeIosGLES.GL_SRC_COLOR); + break; + default: + throw new UnsupportedOperationException("Unrecognized blend mode: " + + state.getBlendMode()); + } + JmeIosGLES.checkGLError(); + } + context.blendMode = state.getBlendMode(); + } + } + + /** + * Set the range of the depth values for objects. All rendered + * objects will have their depth clamped to this range. + * + * @param start The range start + * @param end The range end + */ + public void setDepthRange(float start, float end) { + logger.log(Level.FINE, "IGLESShaderRenderer setDepthRange"); + JmeIosGLES.glDepthRangef(start, end); + JmeIosGLES.checkGLError(); + } + + /** + * Called when a new frame has been rendered. + */ + public void onFrame() { + logger.log(Level.FINE, "IGLESShaderRenderer onFrame"); + //JmeIosGLES.checkGLErrorForced(); + JmeIosGLES.checkGLError(); + + objManager.deleteUnused(this); + } + + /** + * Set the world matrix to use. Does nothing if the Renderer is + * shader based. + * + * @param worldMatrix World matrix to use. + */ + public void setWorldMatrix(Matrix4f worldMatrix) { + logger.log(Level.FINE, "IGLESShaderRenderer setWorldMatrix"); + } + + /** + * Sets the view and projection matrices to use. Does nothing if the Renderer + * is shader based. + * + * @param viewMatrix The view matrix to use. + * @param projMatrix The projection matrix to use. + */ + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + logger.log(Level.FINE, "IGLESShaderRenderer setViewProjectionMatrices"); + } + + /** + * Set the viewport location and resolution on the screen. + * + * @param x The x coordinate of the viewport + * @param y The y coordinate of the viewport + * @param width Width of the viewport + * @param height Height of the viewport + */ + public void setViewPort(int x, int y, int width, int height) { + logger.log(Level.FINE, "IGLESShaderRenderer setViewPort"); + if (x != vpX || vpY != y || vpW != width || vpH != height) { + JmeIosGLES.glViewport(x, y, width, height); + JmeIosGLES.checkGLError(); + + vpX = x; + vpY = y; + vpW = width; + vpH = height; + } + } + + /** + * Specifies a clipping rectangle. + * For all future rendering commands, no pixels will be allowed + * to be rendered outside of the clip rectangle. + * + * @param x The x coordinate of the clip rect + * @param y The y coordinate of the clip rect + * @param width Width of the clip rect + * @param height Height of the clip rect + */ + public void setClipRect(int x, int y, int width, int height) { + logger.log(Level.FINE, "IGLESShaderRenderer setClipRect"); + if (!context.clipRectEnabled) { + JmeIosGLES.glEnable(JmeIosGLES.GL_SCISSOR_TEST); + JmeIosGLES.checkGLError(); + context.clipRectEnabled = true; + } + if (clipX != x || clipY != y || clipW != width || clipH != height) { + JmeIosGLES.glScissor(x, y, width, height); + JmeIosGLES.checkGLError(); + clipX = x; + clipY = y; + clipW = width; + clipH = height; + } + } + + /** + * Clears the clipping rectangle set with + * {@link #setClipRect(int, int, int, int) }. + */ + public void clearClipRect() { + logger.log(Level.FINE, "IGLESShaderRenderer clearClipRect"); + if (context.clipRectEnabled) { + JmeIosGLES.glDisable(JmeIosGLES.GL_SCISSOR_TEST); + JmeIosGLES.checkGLError(); + context.clipRectEnabled = false; + + clipX = 0; + clipY = 0; + clipW = 0; + clipH = 0; + } + } + + /** + * Set lighting state. + * Does nothing if the renderer is shader based. + * The lights should be provided in world space. + * Specify null to disable lighting. + * + * @param lights The light list to set. + */ + public void setLighting(LightList lights) { + logger.log(Level.FINE, "IGLESShaderRenderer setLighting"); + } + + /** + * Sets the shader to use for rendering. + * If the shader has not been uploaded yet, it is compiled + * and linked. If it has been uploaded, then the + * uniform data is updated and the shader is set. + * + * @param shader The shader to use for rendering. + */ + public void setShader(Shader shader) { + logger.log(Level.FINE, "IGLESShaderRenderer setShader"); + if (shader == null) { + throw new IllegalArgumentException("Shader cannot be null"); + } else { + if (shader.isUpdateNeeded()) { + updateShaderData(shader); + } + + // NOTE: might want to check if any of the + // sources need an update? + + assert shader.getId() > 0; + + updateShaderUniforms(shader); + bindProgram(shader); + } + } + + /** + * Deletes a shader. This method also deletes + * the attached shader sources. + * + * @param shader Shader to delete. + */ + public void deleteShader(Shader shader) { + logger.log(Level.FINE, "IGLESShaderRenderer deleteShader"); + if (shader.getId() == -1) { + logger.warning("Shader is not uploaded to GPU, cannot delete."); + return; + } + + for (ShaderSource source : shader.getSources()) { + if (source.getId() != -1) { + JmeIosGLES.glDetachShader(shader.getId(), source.getId()); + JmeIosGLES.checkGLError(); + + deleteShaderSource(source); + } + } + + JmeIosGLES.glDeleteProgram(shader.getId()); + JmeIosGLES.checkGLError(); + + statistics.onDeleteShader(); + shader.resetObject(); + } + + /** + * Deletes the provided shader source. + * + * @param source The ShaderSource to delete. + */ + public void deleteShaderSource(ShaderSource source) { + logger.log(Level.FINE, "IGLESShaderRenderer deleteShaderSource"); + if (source.getId() < 0) { + logger.warning("Shader source is not uploaded to GPU, cannot delete."); + return; + } + + source.clearUpdateNeeded(); + + JmeIosGLES.glDeleteShader(source.getId()); + JmeIosGLES.checkGLError(); + + source.resetObject(); + } + + /** + * Copies contents from src to dst, scaling if necessary. + */ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + logger.log(Level.FINE, "IGLESShaderRenderer copyFrameBuffer"); + copyFrameBuffer(src, dst, true); + } + + /** + * Copies contents from src to dst, scaling if necessary. + * set copyDepth to false to only copy the color buffers. + */ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { + logger.warning("IGLESShaderRenderer copyFrameBuffer with depth TODO"); + throw new RendererException("Copy framebuffer not implemented yet."); + } + + /** + * Sets the framebuffer that will be drawn to. + */ + public void setFrameBuffer(FrameBuffer fb) { + logger.log(Level.FINE, "IGLESShaderRenderer setFrameBuffer"); + if (fb == null && mainFbOverride != null) { + fb = mainFbOverride; + } + + if (lastFb == fb) { + if (fb == null || !fb.isUpdateNeeded()) { + 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()); + +// int textureType = convertTextureType(tex.getType(), tex.getImage().getMultiSamples(), rb.getFace()); + int textureType = convertTextureType(tex.getType()); + JmeIosGLES.glGenerateMipmap(textureType); + JmeIosGLES.checkGLError(); + } + } + } + + if (fb == null) { + // unbind any fbos + if (context.boundFBO != 0) { + JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, 0); + JmeIosGLES.checkGLError(); + + 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.getNumColorBuffers() == 0 && fb.getDepthBuffer() == null) { + throw new IllegalArgumentException("The framebuffer: " + fb + + "\nDoesn't have any color/depth buffers"); + } + + if (fb.isUpdateNeeded()) { + updateFrameBuffer(fb); + } + + if (context.boundFBO != fb.getId()) { + JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, fb.getId()); + JmeIosGLES.checkGLError(); + + 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.getNumColorBuffers() > maxFBOAttachs) { + throw new RendererException("Framebuffer has more color " + + "attachments than are supported" + + " by the video hardware!"); + } + if (fb.isMultiTarget()) { + if (fb.getNumColorBuffers() > maxMRTFBOAttachs) { + throw new RendererException("Framebuffer has more" + + " multi targets than are supported" + + " by the video hardware!"); + } + + if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()) { + //intBuf16.clear(); + for (int i = 0; i < 16; i++) { + intBuf16[i] = i < fb.getNumColorBuffers() ? JmeIosGLES.GL_COLOR_ATTACHMENT0 + i : 0; + } + + //intBuf16.flip();// TODO: flip +// glDrawBuffers(intBuf16); + context.boundDrawBuf = 100 + fb.getNumColorBuffers(); + } + } else { + RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); + // select this draw buffer + if (context.boundDrawBuf != rb.getSlot()) { + JmeIosGLES.glActiveTexture(convertAttachmentSlot(rb.getSlot())); + JmeIosGLES.checkGLError(); + + context.boundDrawBuf = rb.getSlot(); + } + } + } + + assert fb.getId() >= 0; + assert context.boundFBO == fb.getId(); + + lastFb = fb; + + checkFrameBufferStatus(fb); + } + } + + /** + * Set the framebuffer that will be set instead of the main framebuffer + * when a call to setFrameBuffer(null) is made. + * + * @param fb + */ + public void setMainFrameBufferOverride(FrameBuffer fb) { + logger.log(Level.FINE, "IGLESShaderRenderer setMainFrameBufferOverride"); + mainFbOverride = fb; + } + + /** + * Reads the pixels currently stored in the specified framebuffer + * into the given ByteBuffer object. + * Only color pixels are transferred, the format is BGRA with 8 bits + * per component. The given byte buffer should have at least + * fb.getWidth() * fb.getHeight() * 4 bytes remaining. + * + * @param fb The framebuffer to read from + * @param byteBuf The bytebuffer to transfer color data to + */ + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + logger.log(Level.FINE, "IGLESShaderRenderer readFrameBuffer"); + if (fb != null) { + RenderBuffer rb = fb.getColorBuffer(); + if (rb == null) { + throw new IllegalArgumentException("Specified framebuffer" + + " does not have a colorbuffer"); + } + + setFrameBuffer(fb); + } else { + setFrameBuffer(null); + } + + //JmeIosGLES.glReadPixels2(vpX, vpY, vpW, vpH, JmeIosGLES.GL_RGBA, JmeIosGLES.GL_UNSIGNED_BYTE, byteBuf.array(), 0, vpW * vpH * 4); + JmeIosGLES.glReadPixels(vpX, vpY, vpW, vpH, JmeIosGLES.GL_RGBA, JmeIosGLES.GL_UNSIGNED_BYTE, byteBuf); + JmeIosGLES.checkGLError(); + } + + /** + * Deletes a framebuffer and all attached renderbuffers + */ + public void deleteFrameBuffer(FrameBuffer fb) { + logger.log(Level.FINE, "IGLESShaderRenderer deleteFrameBuffer"); + if (fb.getId() != -1) { + if (context.boundFBO == fb.getId()) { + JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, 0); + JmeIosGLES.checkGLError(); + + context.boundFBO = 0; + } + + if (fb.getDepthBuffer() != null) { + deleteRenderBuffer(fb, fb.getDepthBuffer()); + } + if (fb.getColorBuffer() != null) { + deleteRenderBuffer(fb, fb.getColorBuffer()); + } + + intBuf1[0] = fb.getId(); + JmeIosGLES.glDeleteFramebuffers(1, intBuf1, 0); + JmeIosGLES.checkGLError(); + + fb.resetObject(); + + statistics.onDeleteFrameBuffer(); + } + } + + /** + * Sets the texture to use for the given texture unit. + */ + public void setTexture(int unit, Texture tex) { + logger.log(Level.FINE, "IGLESShaderRenderer setTexture"); + Image image = tex.getImage(); + if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated()) ) { + updateTexImageData(image, tex.getType()); + } + + 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) { + JmeIosGLES.glActiveTexture(JmeIosGLES.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + + JmeIosGLES.glBindTexture(type, texId); + JmeIosGLES.checkGLError(); + + textures[unit] = image; + + statistics.onTextureUse(tex.getImage(), true); + } else { + statistics.onTextureUse(tex.getImage(), false); + } + + setupTextureParams(tex); + } + + /** + * Modify the given Texture tex with the given Image. The image will be put at x and y into the texture. + * + * @param tex the Texture that will be modified + * @param pixels the source Image data to copy data from + * @param x the x position to put the image into the texture + * @param y the y position to put the image into the texture + */ + public void modifyTexture(Texture tex, Image pixels, int x, int y) { + logger.log(Level.FINE, "IGLESShaderRenderer modifyTexture"); + setTexture(0, tex); + TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType()), 0, x, y); + } + + /** + * Deletes a texture from the GPU. + */ + public void deleteImage(Image image) { + logger.log(Level.FINE, "IGLESShaderRenderer deleteImage"); + int texId = image.getId(); + if (texId != -1) { + intBuf1[0] = texId; + + JmeIosGLES.glDeleteTextures(1, intBuf1, 0); + JmeIosGLES.checkGLError(); + + image.resetObject(); + + statistics.onDeleteTexture(); + } + } + + /** + * Uploads a vertex buffer to the GPU. + * + * @param vb The vertex buffer to upload + */ + public void updateBufferData(VertexBuffer vb) { + logger.log(Level.FINE, "IGLESShaderRenderer updateBufferData"); + int bufId = vb.getId(); + boolean created = false; + if (bufId == -1) { + // create buffer + JmeIosGLES.glGenBuffers(1, intBuf1, 0); + JmeIosGLES.checkGLError(); + + bufId = intBuf1[0]; + vb.setId(bufId); + objManager.registerObject(vb); + + created = true; + } + + // bind buffer + int target; + if (vb.getBufferType() == VertexBuffer.Type.Index) { + target = JmeIosGLES.GL_ELEMENT_ARRAY_BUFFER; + if (context.boundElementArrayVBO != bufId) { + JmeIosGLES.glBindBuffer(target, bufId); + JmeIosGLES.checkGLError(); + + context.boundElementArrayVBO = bufId; + } + } else { + target = JmeIosGLES.GL_ARRAY_BUFFER; + if (context.boundArrayVBO != bufId) { + JmeIosGLES.glBindBuffer(target, bufId); + JmeIosGLES.checkGLError(); + + context.boundArrayVBO = bufId; + } + } + + int usage = convertUsage(vb.getUsage()); + vb.getData().rewind(); + + if (created || vb.hasDataSizeChanged()) { + // upload data based on format + int size = vb.getData().limit() * vb.getFormat().getComponentSize(); + + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + JmeIosGLES.glBufferData(target, size, (ByteBuffer) vb.getData(), usage); + JmeIosGLES.checkGLError(); + break; + case Short: + case UnsignedShort: + JmeIosGLES.glBufferData(target, size, (ShortBuffer) vb.getData(), usage); + JmeIosGLES.checkGLError(); + break; + case Int: + case UnsignedInt: + JmeIosGLES.glBufferData(target, size, (IntBuffer) vb.getData(), usage); + JmeIosGLES.checkGLError(); + break; + case Float: + JmeIosGLES.glBufferData(target, size, (FloatBuffer) vb.getData(), usage); + JmeIosGLES.checkGLError(); + break; + default: + throw new RuntimeException("Unknown buffer format."); + } + } else { + int size = vb.getData().limit() * vb.getFormat().getComponentSize(); + + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + JmeIosGLES.glBufferSubData(target, 0, size, (ByteBuffer) vb.getData()); + JmeIosGLES.checkGLError(); + break; + case Short: + case UnsignedShort: + JmeIosGLES.glBufferSubData(target, 0, size, (ShortBuffer) vb.getData()); + JmeIosGLES.checkGLError(); + break; + case Int: + case UnsignedInt: + JmeIosGLES.glBufferSubData(target, 0, size, (IntBuffer) vb.getData()); + JmeIosGLES.checkGLError(); + break; + case Float: + JmeIosGLES.glBufferSubData(target, 0, size, (FloatBuffer) vb.getData()); + JmeIosGLES.checkGLError(); + break; + default: + throw new RuntimeException("Unknown buffer format."); + } + } + vb.clearUpdateNeeded(); + } + + /** + * Deletes a vertex buffer from the GPU. + * @param vb The vertex buffer to delete + */ + public void deleteBuffer(VertexBuffer vb) { + logger.log(Level.FINE, "IGLESShaderRenderer deleteBuffer"); + int bufId = vb.getId(); + if (bufId != -1) { + // delete buffer + intBuf1[0] = bufId; + + JmeIosGLES.glDeleteBuffers(1, intBuf1, 0); + JmeIosGLES.checkGLError(); + + vb.resetObject(); + } + } + + /** + * Renders count meshes, with the geometry data supplied. + * The shader which is currently set with setShader is + * responsible for transforming the input verticies into clip space + * and shading it based on the given vertex attributes. + * The int variable gl_InstanceID can be used to access the current + * instance of the mesh being rendered inside the vertex shader. + * + * @param mesh The mesh to render + * @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }. + * @param count Number of mesh instances to render + */ + public void renderMesh(Mesh mesh, int lod, int count) { + logger.log(Level.FINE, "IGLESShaderRenderer renderMesh"); + if (mesh.getVertexCount() == 0) { + return; + } + /* + * NOTE: not supported in OpenGL ES 2.0. + if (context.pointSize != mesh.getPointSize()) { + GLES10.glPointSize(mesh.getPointSize()); + context.pointSize = mesh.getPointSize(); + } + */ + if (context.lineWidth != mesh.getLineWidth()) { + JmeIosGLES.glLineWidth(mesh.getLineWidth()); + JmeIosGLES.checkGLError(); + context.lineWidth = mesh.getLineWidth(); + } + + statistics.onMeshDrawn(mesh, lod); +// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){ +// renderMeshVertexArray(mesh, lod, count); +// }else{ + + if (useVBO) { + renderMeshDefault(mesh, lod, count); + } else { + renderMeshVertexArray(mesh, lod, count); + } + } + + /** + * Resets all previously used {@link NativeObject Native Objects} on this Renderer. + * The state of the native objects is reset in such way, that using + * them again will cause the renderer to reupload them. + * Call this method when you know the GL context is going to shutdown. + * + * @see NativeObject#resetObject() + */ + public void resetGLObjects() { + logger.log(Level.FINE, "IGLESShaderRenderer resetGLObjects"); + objManager.resetObjects(); + statistics.clearMemory(); + boundShader = null; + lastFb = null; + context.reset(); + } + + /** + * Deletes all previously used {@link NativeObject Native Objects} on this Renderer, and + * then resets the native objects. + * + * @see #resetGLObjects() + * @see NativeObject#deleteObject(java.lang.Object) + */ + public void cleanup() { + logger.log(Level.FINE, "IGLESShaderRenderer cleanup"); + objManager.deleteAllObjects(this); + statistics.clearMemory(); + } + + /** + * Sets the alpha to coverage state. + *

+ * When alpha coverage and multi-sampling is enabled, + * each pixel will contain alpha coverage in all + * of its subsamples, which is then combined when + * other future alpha-blended objects are rendered. + *

+ *

+ * Alpha-to-coverage is useful for rendering transparent objects + * without having to worry about sorting them. + *

+ */ + public void setAlphaToCoverage(boolean value) { + logger.log(Level.FINE, "IGLESShaderRenderer setAlphaToCoverage"); + if (value) { + JmeIosGLES.glEnable(JmeIosGLES.GL_SAMPLE_ALPHA_TO_COVERAGE); + JmeIosGLES.checkGLError(); + } else { + JmeIosGLES.glDisable(JmeIosGLES.GL_SAMPLE_ALPHA_TO_COVERAGE); + JmeIosGLES.checkGLError(); + } + } + + + /* ------------------------------------------------------------------------------ */ + + + public void initialize() { + Level store = logger.getLevel(); + logger.setLevel(Level.FINE); + + logger.log(Level.FINE, "Vendor: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_VENDOR)); + logger.log(Level.FINE, "Renderer: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_RENDERER)); + logger.log(Level.FINE, "Version: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_VERSION)); + logger.log(Level.FINE, "Shading Language Version: {0}", JmeIosGLES.glGetString(JmeIosGLES.GL_SHADING_LANGUAGE_VERSION)); + + /* + // Fix issue in TestRenderToMemory when GL_FRONT is the main + // buffer being used. + initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); + initialReadBuf = glGetInteger(GL_READ_BUFFER); + + // XXX: This has to be GL_BACK for canvas on Mac + // Since initialDrawBuf is GL_FRONT for pbuffer, gotta + // change this value later on ... +// initialDrawBuf = GL_BACK; +// initialReadBuf = GL_BACK; + */ + + // Check OpenGL version + int openGlVer = extractVersion("OpenGL ES ", JmeIosGLES.glGetString(JmeIosGLES.GL_VERSION)); + if (openGlVer == -1) { + glslVer = -1; + throw new UnsupportedOperationException("OpenGL ES 2.0+ is required for IGLESShaderRenderer!"); + } + + // Check shader language version + glslVer = extractVersion("OpenGL ES GLSL ES ", JmeIosGLES.glGetString(JmeIosGLES.GL_SHADING_LANGUAGE_VERSION)); + switch (glslVer) { + // TODO: When new versions of OpenGL ES shader language come out, + // update this. + default: + caps.add(Caps.GLSL100); + break; + } + + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16, 0); + vertexTextureUnits = intBuf16[0]; + logger.log(Level.FINE, "VTF Units: {0}", vertexTextureUnits); + if (vertexTextureUnits > 0) { + caps.add(Caps.VertexTextureFetch); + } + + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16, 0); + fragTextureUnits = intBuf16[0]; + logger.log(Level.FINE, "Texture Units: {0}", fragTextureUnits); + + // Multiply vector count by 4 to get float count. + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VERTEX_UNIFORM_VECTORS, intBuf16, 0); + vertexUniforms = intBuf16[0] * 4; + logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); + + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_FRAGMENT_UNIFORM_VECTORS, intBuf16, 0); + fragUniforms = intBuf16[0] * 4; + logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); + + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VARYING_VECTORS, intBuf16, 0); + int varyingFloats = intBuf16[0] * 4; + logger.log(Level.FINER, "Varying Floats: {0}", varyingFloats); + + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_VERTEX_ATTRIBS, intBuf16, 0); + vertexAttribs = intBuf16[0]; + logger.log(Level.FINE, "Vertex Attributes: {0}", vertexAttribs); + + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_SUBPIXEL_BITS, intBuf16, 0); + int subpixelBits = intBuf16[0]; + logger.log(Level.FINE, "Subpixel Bits: {0}", subpixelBits); + +// GLES10.glGetIntegerv(GLES10.GL_MAX_ELEMENTS_VERTICES, intBuf16); +// maxVertCount = intBuf16.get(0); +// logger.log(Level.FINER, "Preferred Batch Vertex Count: {0}", maxVertCount); +// +// GLES10.glGetIntegerv(GLES10.GL_MAX_ELEMENTS_INDICES, intBuf16); +// maxTriCount = intBuf16.get(0); +// logger.log(Level.FINER, "Preferred Batch Index Count: {0}", maxTriCount); + + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_TEXTURE_SIZE, intBuf16, 0); + maxTexSize = intBuf16[0]; + logger.log(Level.FINE, "Maximum Texture Resolution: {0}", maxTexSize); + + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16, 0); + maxCubeTexSize = intBuf16[0]; + logger.log(Level.FINE, "Maximum CubeMap Resolution: {0}", maxCubeTexSize); + + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_MAX_RENDERBUFFER_SIZE, intBuf16, 0); + maxRBSize = intBuf16[0]; + logger.log(Level.FINER, "FBO RB Max Size: {0}", maxRBSize); + + /* + 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_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_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 = JmeIosGLES.glGetString(JmeIosGLES.GL_EXTENSIONS); + logger.log(Level.FINE, "GL_EXTENSIONS: {0}", extensions); + + // Get number of compressed formats available. + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_NUM_COMPRESSED_TEXTURE_FORMATS, intBuf16, 0); + int numCompressedFormats = intBuf16[0]; + + // Allocate buffer for compressed formats. + int[] compressedFormats = new int[numCompressedFormats]; + JmeIosGLES.glGetIntegerv(JmeIosGLES.GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats, 0); + + // Check for errors after all glGet calls. + JmeIosGLES.checkGLError(); + + // Print compressed formats. + for (int i = 0; i < numCompressedFormats; i++) { + logger.log(Level.FINE, "Compressed Texture Formats: {0}", compressedFormats[i]); + } + + TextureUtil.loadTextureFeatures(extensions); + + applyRenderState(RenderState.DEFAULT); + JmeIosGLES.glDisable(JmeIosGLES.GL_DITHER); + JmeIosGLES.checkGLError(); + + logger.log(Level.FINE, "Caps: {0}", caps); + logger.setLevel(store); + } + + + /* ------------------------------------------------------------------------------ */ + + + private int extractVersion(String prefixStr, String versionStr) { + if (versionStr != null) { + int spaceIdx = versionStr.indexOf(" ", prefixStr.length()); + if (spaceIdx >= 1) { + versionStr = versionStr.substring(prefixStr.length(), spaceIdx).trim(); + } else { + versionStr = versionStr.substring(prefixStr.length()).trim(); + } + float version = Float.parseFloat(versionStr); + return (int) (version * 100); + } else { + return -1; + } + } + + private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + intBuf1[0] = rb.getId(); + JmeIosGLES.glDeleteRenderbuffers(1, intBuf1, 0); + JmeIosGLES.checkGLError(); + } + + private int convertUsage(Usage usage) { + switch (usage) { + case Static: + return JmeIosGLES.GL_STATIC_DRAW; + case Dynamic: + return JmeIosGLES.GL_DYNAMIC_DRAW; + case Stream: + return JmeIosGLES.GL_STREAM_DRAW; + default: + throw new RuntimeException("Unknown usage type."); + } + } + + + protected void bindProgram(Shader shader) { + int shaderId = shader.getId(); + if (context.boundShaderProgram != shaderId) { + JmeIosGLES.glUseProgram(shaderId); + JmeIosGLES.checkGLError(); + + statistics.onShaderUse(shader, true); + boundShader = shader; + context.boundShaderProgram = shaderId; + } else { + statistics.onShaderUse(shader, false); + } + } + + protected void updateShaderUniforms(Shader shader) { + ListMap uniforms = shader.getUniformMap(); + for (int i = 0; i < uniforms.size(); i++) { + Uniform uniform = uniforms.getValue(i); + if (uniform.isUpdateNeeded()) { + updateUniform(shader, uniform); + } + } + } + + + protected void updateUniform(Shader shader, Uniform uniform) { + logger.log(Level.FINE, "IGLESShaderRenderer private updateUniform: " + uniform.getVarType()); + int shaderId = shader.getId(); + + assert uniform.getName() != null; + assert shader.getId() > 0; + + if (context.boundShaderProgram != shaderId) { + JmeIosGLES.glUseProgram(shaderId); + JmeIosGLES.checkGLError(); + + statistics.onShaderUse(shader, true); + boundShader = shader; + context.boundShaderProgram = shaderId; + } else { + statistics.onShaderUse(shader, false); + } + + int loc = uniform.getLocation(); + if (loc == -1) { + return; + } + + if (loc == -2) { + // get uniform location + updateUniformLocation(shader, uniform); + if (uniform.getLocation() == -1) { + // not declared, ignore + uniform.clearUpdateNeeded(); + return; + } + loc = uniform.getLocation(); + } + + if (uniform.getVarType() == null) { + // removed logging the warning to avoid flooding the log + // (LWJGL also doesn't post a warning) + //logger.log(Level.FINEST, "Uniform value is not set yet. Shader: {0}, Uniform: {1}", + // new Object[]{shader.toString(), uniform.toString()}); + return; // value not set yet.. + } + + statistics.onUniformSet(); + + uniform.clearUpdateNeeded(); + ByteBuffer bb;//GetPrimitiveArrayCritical + FloatBuffer fb; + IntBuffer ib; + switch (uniform.getVarType()) { + case Float: + Float f = (Float) uniform.getValue(); + JmeIosGLES.glUniform1f(loc, f.floatValue()); + break; + case Vector2: + Vector2f v2 = (Vector2f) uniform.getValue(); + JmeIosGLES.glUniform2f(loc, v2.getX(), v2.getY()); + break; + case Vector3: + Vector3f v3 = (Vector3f) uniform.getValue(); + JmeIosGLES.glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); + break; + case Vector4: + Object val = uniform.getValue(); + if (val instanceof ColorRGBA) { + ColorRGBA c = (ColorRGBA) val; + JmeIosGLES.glUniform4f(loc, c.r, c.g, c.b, c.a); + } else if (val instanceof Vector4f) { + Vector4f c = (Vector4f) val; + JmeIosGLES.glUniform4f(loc, c.x, c.y, c.z, c.w); + } else { + Quaternion c = (Quaternion) uniform.getValue(); + JmeIosGLES.glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); + } + break; + case Boolean: + Boolean b = (Boolean) uniform.getValue(); + JmeIosGLES.glUniform1i(loc, b.booleanValue() ? JmeIosGLES.GL_TRUE : JmeIosGLES.GL_FALSE); + break; + case Matrix3: + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 9; + JmeIosGLES.glUniformMatrix3fv(loc, 1, false, fb); + break; + case Matrix4: + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 16; + JmeIosGLES.glUniformMatrix4fv(loc, 1, false, fb); + break; + case IntArray: + ib = (IntBuffer) uniform.getValue(); + JmeIosGLES.glUniform1iv(loc, ib.limit(), ib); + break; + case FloatArray: + fb = (FloatBuffer) uniform.getValue(); + JmeIosGLES.glUniform1fv(loc, fb.limit(), fb); + break; + case Vector2Array: + fb = (FloatBuffer) uniform.getValue(); + JmeIosGLES.glUniform2fv(loc, fb.limit() / 2, fb); + break; + case Vector3Array: + fb = (FloatBuffer) uniform.getValue(); + JmeIosGLES.glUniform3fv(loc, fb.limit() / 3, fb); + break; + case Vector4Array: + fb = (FloatBuffer) uniform.getValue(); + JmeIosGLES.glUniform4fv(loc, fb.limit() / 4, fb); + break; + case Matrix4Array: + fb = (FloatBuffer) uniform.getValue(); + JmeIosGLES.glUniformMatrix4fv(loc, fb.limit() / 16, false, fb); + break; + case Int: + Integer i = (Integer) uniform.getValue(); + JmeIosGLES.glUniform1i(loc, i.intValue()); + break; + default: + throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); + } + JmeIosGLES.checkGLError(); + } + + protected void updateUniformLocation(Shader shader, Uniform uniform) { + stringBuf.setLength(0); + stringBuf.append(uniform.getName()).append('\0'); + updateNameBuffer(); + int loc = JmeIosGLES.glGetUniformLocation(shader.getId(), uniform.getName()); + JmeIosGLES.checkGLError(); + + if (loc < 0) { + uniform.setLocation(-1); + // uniform is not declared in shader + } else { + uniform.setLocation(loc); + } + } + + 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(); + } + + + public void updateShaderData(Shader shader) { + int id = shader.getId(); + boolean needRegister = false; + if (id == -1) { + // create program + id = JmeIosGLES.glCreateProgram(); + JmeIosGLES.checkGLError(); + + 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); + } + + JmeIosGLES.glAttachShader(id, source.getId()); + JmeIosGLES.checkGLError(); + } + + // link shaders to program + JmeIosGLES.glLinkProgram(id); + JmeIosGLES.checkGLError(); + + JmeIosGLES.glGetProgramiv(id, JmeIosGLES.GL_LINK_STATUS, intBuf1, 0); + JmeIosGLES.checkGLError(); + + boolean linkOK = intBuf1[0] == JmeIosGLES.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !linkOK) { + JmeIosGLES.glGetProgramiv(id, JmeIosGLES.GL_INFO_LOG_LENGTH, intBuf1, 0); + JmeIosGLES.checkGLError(); + + int length = intBuf1[0]; + if (length > 3) { + // get infos + infoLog = JmeIosGLES.glGetProgramInfoLog(id); + JmeIosGLES.checkGLError(); + } + } + + if (linkOK) { + if (infoLog != null) { + logger.log(Level.FINE, "shader link success. \n{0}", infoLog); + } else { + logger.fine("shader link success"); + } + shader.clearUpdateNeeded(); + if (needRegister) { + // Register shader for clean up if it was created in this method. + objManager.registerObject(shader); + statistics.onNewShader(); + } else { + // OpenGL spec: uniform locations may change after re-link + resetUniformLocations(shader); + } + } else { + if (infoLog != null) { + throw new RendererException("Shader link failure, shader:" + shader + " info:" + infoLog); + } else { + throw new RendererException("Shader link failure, shader:" + shader + " info: "); + } + } + } + + protected void resetUniformLocations(Shader shader) { + ListMap uniforms = shader.getUniformMap(); + for (int i = 0; i < uniforms.size(); i++) { + Uniform uniform = uniforms.getValue(i); + uniform.reset(); // e.g check location again + } + } + + public void updateTexImageData(Image img, Texture.Type type) { + int texId = img.getId(); + if (texId == -1) { + // create texture + JmeIosGLES.glGenTextures(1, intBuf1, 0); + JmeIosGLES.checkGLError(); + + texId = intBuf1[0]; + img.setId(texId); + objManager.registerObject(img); + + statistics.onNewTexture(); + } + + // bind texture + int target = convertTextureType(type); + if (context.boundTextures[0] != img) { + if (context.boundTextureUnit != 0) { + JmeIosGLES.glActiveTexture(JmeIosGLES.GL_TEXTURE0); + JmeIosGLES.checkGLError(); + + context.boundTextureUnit = 0; + } + + JmeIosGLES.glBindTexture(target, texId); + JmeIosGLES.checkGLError(); + + context.boundTextures[0] = img; + } + + boolean needMips = false; + if (img.isGeneratedMipmapsRequired()) { + needMips = true; + img.setMipmapsGenerated(true); + } + + if (target == JmeIosGLES.GL_TEXTURE_CUBE_MAP) { + // Check max texture size before upload + if (img.getWidth() > maxCubeTexSize || img.getHeight() > maxCubeTexSize) { + throw new RendererException("Cannot upload cubemap " + img + ". The maximum supported cubemap resolution is " + maxCubeTexSize); + } + } else { + if (img.getWidth() > maxTexSize || img.getHeight() > maxTexSize) { + throw new RendererException("Cannot upload texture " + img + ". The maximum supported texture resolution is " + maxTexSize); + } + } + + if (target == JmeIosGLES.GL_TEXTURE_CUBE_MAP) { + // Upload a cube map / sky box + /* + @SuppressWarnings("unchecked") + List bmps = (List) 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(JmeIosGLES.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, bmps.get(i).getBitmap(), needMips); + bmps.get(i).notifyBitmapUploaded(); + } + } else { + */ + // Standard jme3 image data + List data = img.getData(); + if (data.size() != 6) { + throw new UnsupportedOperationException("Invalid texture: " + img + + "Cubemap textures must contain 6 data units."); + } + for (int i = 0; i < 6; i++) { + TextureUtil.uploadTextureAny(img, JmeIosGLES.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, needMips); + } + //} + } else { + TextureUtil.uploadTextureAny(img, target, 0, needMips); + /* + if (img.getEfficentData() instanceof AndroidImageInfo) { + AndroidImageInfo info = (AndroidImageInfo) img.getEfficentData(); + info.notifyBitmapUploaded(); + } + */ + } + + img.clearUpdateNeeded(); + } + + private void setupTextureParams(Texture tex) { + int target = convertTextureType(tex.getType()); + + // filter things + int minFilter = convertMinFilter(tex.getMinFilter()); + int magFilter = convertMagFilter(tex.getMagFilter()); + + JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_MIN_FILTER, minFilter); + JmeIosGLES.checkGLError(); + JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_MAG_FILTER, magFilter); + JmeIosGLES.checkGLError(); + + /* + 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: + JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); + + // fall down here is intentional.. +// case OneDimensional: + JmeIosGLES.glTexParameteri(target, JmeIosGLES.GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); + + JmeIosGLES.checkGLError(); + 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); + } + } + */ + } + + private int convertTextureType(Texture.Type type) { + switch (type) { + case TwoDimensional: + return JmeIosGLES.GL_TEXTURE_2D; + // case TwoDimensionalArray: + // return EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT; +// case ThreeDimensional: + // return GLES20.GL_TEXTURE_3D; + case CubeMap: + return JmeIosGLES.GL_TEXTURE_CUBE_MAP; + default: + throw new UnsupportedOperationException("Unknown texture type: " + type); + } + } + + private int convertMagFilter(Texture.MagFilter filter) { + switch (filter) { + case Bilinear: + return JmeIosGLES.GL_LINEAR; + case Nearest: + return JmeIosGLES.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown mag filter: " + filter); + } + } + + private int convertMinFilter(Texture.MinFilter filter) { + switch (filter) { + case Trilinear: + return JmeIosGLES.GL_LINEAR_MIPMAP_LINEAR; + case BilinearNearestMipMap: + return JmeIosGLES.GL_LINEAR_MIPMAP_NEAREST; + case NearestLinearMipMap: + return JmeIosGLES.GL_NEAREST_MIPMAP_LINEAR; + case NearestNearestMipMap: + return JmeIosGLES.GL_NEAREST_MIPMAP_NEAREST; + case BilinearNoMipMaps: + return JmeIosGLES.GL_LINEAR; + case NearestNoMipMaps: + return JmeIosGLES.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown min filter: " + filter); + } + } + + private int convertWrapMode(Texture.WrapMode mode) { + switch (mode) { + case BorderClamp: + case Clamp: + case EdgeClamp: + return JmeIosGLES.GL_CLAMP_TO_EDGE; + case Repeat: + return JmeIosGLES.GL_REPEAT; + case MirroredRepeat: + return JmeIosGLES.GL_MIRRORED_REPEAT; + default: + throw new UnsupportedOperationException("Unknown wrap mode: " + mode); + } + } + + private void renderMeshVertexArray(Mesh mesh, int lod, int count) { + 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 { + JmeIosGLES.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + JmeIosGLES.checkGLError(); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + private void renderMeshDefault(Mesh mesh, int lod, int count) { + VertexBuffer indices = null; + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + + //IntMap 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"); + JmeIosGLES.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + JmeIosGLES.checkGLError(); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + + public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { + if (vb.getBufferType() == VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); + } + + if (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) { + 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(); + loc = JmeIosGLES.glGetAttribLocation(programId, attributeName); + JmeIosGLES.checkGLError(); + + // 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); + return; // not available in shader. + } else { + attrib.setLocation(loc); + } + } + + VertexBuffer[] attribs = context.boundAttribs; + if (!context.attribIndexList.moveToNew(loc)) { + JmeIosGLES.glEnableVertexAttribArray(loc); + JmeIosGLES.checkGLError(); + //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) { + JmeIosGLES.glBindBuffer(JmeIosGLES.GL_ARRAY_BUFFER, bufId); + JmeIosGLES.checkGLError(); + + context.boundArrayVBO = bufId; + } + + vb.getData().rewind(); + /* + Android22Workaround.glVertexAttribPointer(loc, + vb.getNumComponents(), + convertVertexBufferFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + 0); + */ + logger.warning("iTODO Android22Workaround"); + + JmeIosGLES.glVertexAttribPointer(loc, + vb.getNumComponents(), + convertVertexBufferFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + null); + + JmeIosGLES.checkGLError(); + + 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{*/ + JmeIosGLES.glDrawArrays(convertElementMode(mode), 0, vertCount); + JmeIosGLES.checkGLError(); + /* + }*/ + } + + public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) { + if (indexBuf.getBufferType() != VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Only index buffers are allowed as triangle lists."); + } + + if (indexBuf.isUpdateNeeded()) { + updateBufferData(indexBuf); + } + + int bufId = indexBuf.getId(); + assert bufId != -1; + + if (bufId == -1) { + throw new RendererException("Invalid buffer ID"); + } + + if (context.boundElementArrayVBO != bufId) { + JmeIosGLES.glBindBuffer(JmeIosGLES.GL_ELEMENT_ARRAY_BUFFER, bufId); + JmeIosGLES.checkGLError(); + + context.boundElementArrayVBO = bufId; + } + + int vertCount = mesh.getVertexCount(); + boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); + + Buffer indexData = indexBuf.getData(); + + if (indexBuf.getFormat() == Format.UnsignedInt) { + throw new RendererException("OpenGL ES does not support 32-bit index buffers." + + "Split your models to avoid going over 65536 vertices."); + } + + if (mesh.getMode() == Mode.Hybrid) { + int[] modeStart = mesh.getModeStart(); + int[] elementLengths = mesh.getElementLengths(); + + int elMode = convertElementMode(Mode.Triangles); + int fmt = convertVertexBufferFormat(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); + JmeIosGLES.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); + JmeIosGLES.checkGLError(); + /* + 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().limit(), + convertVertexBufferFormat(indexBuf.getFormat()), + 0, + count); + */ + } else { + logger.log(Level.FINE, "IGLESShaderRenderer drawTriangleList TODO check"); + indexData.rewind(); + JmeIosGLES.glDrawElementsIndex( + convertElementMode(mesh.getMode()), + indexBuf.getData().limit(), + convertVertexBufferFormat(indexBuf.getFormat()), + 0); + /*TODO: + indexData.rewind(); + JmeIosGLES.glDrawElements( + convertElementMode(mesh.getMode()), + indexBuf.getData().limit(), + convertVertexBufferFormat(indexBuf.getFormat()), + 0); + */ + JmeIosGLES.checkGLError(); + } + } + } + + public int convertElementMode(Mesh.Mode mode) { + switch (mode) { + case Points: + return JmeIosGLES.GL_POINTS; + case Lines: + return JmeIosGLES.GL_LINES; + case LineLoop: + return JmeIosGLES.GL_LINE_LOOP; + case LineStrip: + return JmeIosGLES.GL_LINE_STRIP; + case Triangles: + return JmeIosGLES.GL_TRIANGLES; + case TriangleFan: + return JmeIosGLES.GL_TRIANGLE_FAN; + case TriangleStrip: + return JmeIosGLES.GL_TRIANGLE_STRIP; + default: + throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); + } + } + + + private int convertVertexBufferFormat(Format format) { + switch (format) { + case Byte: + return JmeIosGLES.GL_BYTE; + case UnsignedByte: + return JmeIosGLES.GL_UNSIGNED_BYTE; + case Short: + return JmeIosGLES.GL_SHORT; + case UnsignedShort: + return JmeIosGLES.GL_UNSIGNED_SHORT; + case Int: + return JmeIosGLES.GL_INT; + case UnsignedInt: + return JmeIosGLES.GL_UNSIGNED_INT; + /* + case Half: + return NVHalfFloat.GL_HALF_FLOAT_NV; + // return ARBHalfFloatVertex.GL_HALF_FLOAT; + */ + case Float: + return JmeIosGLES.GL_FLOAT; +// case Double: +// return JmeIosGLES.GL_DOUBLE; + default: + throw new RuntimeException("Unknown buffer format."); + + } + } + + public void clearVertexAttribs() { + IDList attribList = context.attribIndexList; + for (int i = 0; i < attribList.oldLen; i++) { + int idx = attribList.oldList[i]; + + JmeIosGLES.glDisableVertexAttribArray(idx); + JmeIosGLES.checkGLError(); + + context.boundAttribs[idx] = null; + } + context.attribIndexList.copyNewToOld(); + } + + + 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 updateFrameBuffer(FrameBuffer fb) { + int id = fb.getId(); + if (id == -1) { + // create FBO + JmeIosGLES.glGenFramebuffers(1, intBuf1, 0); + JmeIosGLES.checkGLError(); + + id = intBuf1[0]; + fb.setId(id); + objManager.registerObject(fb); + + statistics.onNewFrameBuffer(); + } + + if (context.boundFBO != id) { + JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, id); + JmeIosGLES.checkGLError(); + + // 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 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) { + JmeIosGLES.glFramebufferRenderbuffer(JmeIosGLES.GL_FRAMEBUFFER, + convertAttachmentSlot(rb.getSlot()), + JmeIosGLES.GL_RENDERBUFFER, + rb.getId()); + + JmeIosGLES.checkGLError(); + } + } + + + public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) { + Texture tex = rb.getTexture(); + Image image = tex.getImage(); + if (image.isUpdateNeeded()) { + updateTexImageData(image, tex.getType()); + + // NOTE: For depth textures, sets nearest/no-mips mode + // Required to fix "framebuffer unsupported" + // for old NVIDIA drivers! + setupTextureParams(tex); + } + + JmeIosGLES.glFramebufferTexture2D(JmeIosGLES.GL_FRAMEBUFFER, + convertAttachmentSlot(rb.getSlot()), + convertTextureType(tex.getType()), + image.getId(), + 0); + + JmeIosGLES.checkGLError(); + } + + + private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + int id = rb.getId(); + if (id == -1) { + JmeIosGLES.glGenRenderbuffers(1, intBuf1, 0); + JmeIosGLES.checkGLError(); + + id = intBuf1[0]; + rb.setId(id); + } + + if (context.boundRB != id) { + JmeIosGLES.glBindRenderbuffer(JmeIosGLES.GL_RENDERBUFFER, id); + JmeIosGLES.checkGLError(); + + context.boundRB = id; + } + + if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize) { + throw new RendererException("Resolution " + fb.getWidth() + + ":" + fb.getHeight() + " is not supported."); + } + + TextureUtil.IosGLImageFormat imageFormat = TextureUtil.getImageFormat(rb.getFormat()); + if (imageFormat.renderBufferStorageFormat == 0) { + throw new RendererException("The format '" + rb.getFormat() + "' cannot be used for renderbuffers."); + } + +// if (fb.getSamples() > 1 && GLContext.getCapabilities().GL_EXT_framebuffer_multisample) { + if (fb.getSamples() > 1) { +// // FIXME + throw new RendererException("Multisample FrameBuffer is not supported yet."); +// int samples = fb.getSamples(); +// if (maxFBOSamples < samples) { +// samples = maxFBOSamples; +// } +// glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, +// samples, +// glFmt.internalFormat, +// fb.getWidth(), +// fb.getHeight()); + } else { + JmeIosGLES.glRenderbufferStorage(JmeIosGLES.GL_RENDERBUFFER, + imageFormat.renderBufferStorageFormat, + fb.getWidth(), + fb.getHeight()); + + JmeIosGLES.checkGLError(); + } + } + + + private int convertAttachmentSlot(int attachmentSlot) { + // can also add support for stencil here + if (attachmentSlot == -100) { + return JmeIosGLES.GL_DEPTH_ATTACHMENT; + } else if (attachmentSlot == 0) { + return JmeIosGLES.GL_COLOR_ATTACHMENT0; + } else { + throw new UnsupportedOperationException("Android does not support multiple color attachments to an FBO"); + } + } + + + private void checkFrameBufferStatus(FrameBuffer fb) { + try { + checkFrameBufferError(); + } catch (IllegalStateException ex) { + logger.log(Level.SEVERE, "=== jMonkeyEngine FBO State ===\n{0}", fb); + printRealFrameBufferInfo(fb); + throw ex; + } + } + + private void checkFrameBufferError() { + int status = JmeIosGLES.glCheckFramebufferStatus(JmeIosGLES.GL_FRAMEBUFFER); + switch (status) { + case JmeIosGLES.GL_FRAMEBUFFER_COMPLETE: + break; + case JmeIosGLES.GL_FRAMEBUFFER_UNSUPPORTED: + //Choose different formats + throw new IllegalStateException("Framebuffer object format is " + + "unsupported by the video hardware."); + case JmeIosGLES.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + throw new IllegalStateException("Framebuffer has erronous attachment."); + case JmeIosGLES.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached."); + case JmeIosGLES.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + throw new IllegalStateException("Framebuffer attachments must have same dimensions."); +// case GLES20.GL_FRAMEBUFFER_INCOMPLETE_FORMATS: +// throw new IllegalStateException("Framebuffer attachments must have same formats."); +// case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: +// throw new IllegalStateException("Incomplete draw buffer."); +// case GLES20.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: +// throw new IllegalStateException("Incomplete read buffer."); +// case GLES20.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: " + status); + } + } + + private void printRealRenderBufferInfo(FrameBuffer fb, RenderBuffer rb, String name) { + System.out.println("== Renderbuffer " + name + " =="); + System.out.println("RB ID: " + rb.getId()); + System.out.println("Is proper? " + JmeIosGLES.glIsRenderbuffer(rb.getId())); + + int attachment = convertAttachmentSlot(rb.getSlot()); + + //intBuf16.clear(); + JmeIosGLES.glGetFramebufferAttachmentParameteriv(JmeIosGLES.GL_FRAMEBUFFER, + attachment, JmeIosGLES.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, intBuf16, 0); + int type = intBuf16[0]; + + //intBuf16.clear(); + JmeIosGLES.glGetFramebufferAttachmentParameteriv(JmeIosGLES.GL_FRAMEBUFFER, + attachment, JmeIosGLES.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, intBuf16, 0); + int rbName = intBuf16[0]; + + switch (type) { + case JmeIosGLES.GL_NONE: + System.out.println("Type: None"); + break; + case JmeIosGLES.GL_TEXTURE: + System.out.println("Type: Texture"); + break; + case JmeIosGLES.GL_RENDERBUFFER: + System.out.println("Type: Buffer"); + System.out.println("RB ID: " + rbName); + break; + } + } + + private void printRealFrameBufferInfo(FrameBuffer fb) { +// boolean doubleBuffer = GLES20.glGetBooleanv(GLES20.GL_DOUBLEBUFFER); + boolean doubleBuffer = false; // FIXME +// String drawBuf = getTargetBufferName(glGetInteger(GL_DRAW_BUFFER)); +// String readBuf = getTargetBufferName(glGetInteger(GL_READ_BUFFER)); + + int fbId = fb.getId(); + //intBuf16.clear(); +// int curDrawBinding = GLES20.glGetIntegerv(GLES20.GL_DRAW_FRAMEBUFFER_BINDING); +// int curReadBinding = glGetInteger(ARBFramebufferObject.GL_READ_FRAMEBUFFER_BINDING); + + System.out.println("=== OpenGL FBO State ==="); + System.out.println("Context doublebuffered? " + doubleBuffer); + System.out.println("FBO ID: " + fbId); + System.out.println("Is proper? " + JmeIosGLES.glIsFramebuffer(fbId)); +// System.out.println("Is bound to draw? " + (fbId == curDrawBinding)); +// System.out.println("Is bound to read? " + (fbId == curReadBinding)); +// System.out.println("Draw buffer: " + drawBuf); +// System.out.println("Read buffer: " + readBuf); + + if (context.boundFBO != fbId) { + JmeIosGLES.glBindFramebuffer(JmeIosGLES.GL_FRAMEBUFFER, fbId); + context.boundFBO = fbId; + } + + if (fb.getDepthBuffer() != null) { + printRealRenderBufferInfo(fb, fb.getDepthBuffer(), "Depth"); + } + for (int i = 0; i < fb.getNumColorBuffers(); i++) { + printRealRenderBufferInfo(fb, fb.getColorBuffer(i), "Color" + i); + } + + + } + + public void drawTriangleList_Array(VertexBuffer indexBuf, Mesh mesh, int 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.rewind(); + + if (mesh.getMode() == Mode.Hybrid) { + int[] modeStart = mesh.getModeStart(); + int[] elementLengths = mesh.getElementLengths(); + + int elMode = convertElementMode(Mode.Triangles); + int fmt = convertVertexBufferFormat(indexBuf.getFormat()); + int elSize = indexBuf.getFormat().getComponentSize(); + int listStart = modeStart[0]; + int stripStart = modeStart[1]; + int fanStart = modeStart[2]; + int curOffset = 0; + for (int i = 0; i < elementLengths.length; i++) { + if (i == stripStart) { + elMode = convertElementMode(Mode.TriangleStrip); + } else if (i == fanStart) { + elMode = convertElementMode(Mode.TriangleFan); + } + int elementLength = elementLengths[i]; + + indexBuf.getData().position(curOffset); + JmeIosGLES.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); + JmeIosGLES.checkGLError(); + + curOffset += elementLength * elSize; + } + } else { + JmeIosGLES.glDrawElements( + convertElementMode(mesh.getMode()), + indexBuf.getData().limit(), + convertVertexBufferFormat(indexBuf.getFormat()), + indexBuf.getData()); + JmeIosGLES.checkGLError(); + } + } + + public void setVertexAttrib_Array(VertexBuffer vb, VertexBuffer 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() + "]"); + return; + } else if (loc == -2) { + String attributeName = "in" + vb.getBufferType().name(); + + loc = JmeIosGLES.glGetAttribLocation(programId, attributeName); + JmeIosGLES.checkGLError(); + + if (loc < 0) { + attrib.setLocation(-1); + 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().rewind(); + avb.getData().position(vb.getOffset()); + + // Upload attribute data + JmeIosGLES.glVertexAttribPointer(loc, + vb.getNumComponents(), + convertVertexBufferFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + avb.getData()); + + JmeIosGLES.checkGLError(); + + JmeIosGLES.glEnableVertexAttribArray(loc); + JmeIosGLES.checkGLError(); + + attribs[loc] = vb; + } // if (attribs[loc] != vb) + } else { + throw new IllegalStateException("Cannot render mesh without shader bound"); + } + } + + public void setVertexAttrib_Array(VertexBuffer vb) { + setVertexAttrib_Array(vb, null); + } + + + public void updateShaderSourceData(ShaderSource source) { + int id = source.getId(); + if (id == -1) { + // Create id + id = JmeIosGLES.glCreateShader(convertShaderType(source.getType())); + JmeIosGLES.checkGLError(); + + if (id <= 0) { + throw new RendererException("Invalid ID received when trying to create shader."); + } + source.setId(id); + } + + if (!source.getLanguage().equals("GLSL100")) { + throw new RendererException("This shader cannot run in OpenGL ES. " + + "Only GLSL 1.0 shaders are supported."); + } + + // upload shader source + // merge the defines and source code + byte[] definesCodeData = source.getDefines().getBytes(); + byte[] sourceCodeData = source.getSource().getBytes(); + ByteBuffer codeBuf = BufferUtils.createByteBuffer(definesCodeData.length + + sourceCodeData.length); + codeBuf.put(definesCodeData); + codeBuf.put(sourceCodeData); + codeBuf.flip(); + + if (powerVr && source.getType() == ShaderType.Vertex) { + // XXX: This is to fix a bug in old PowerVR, remove + // when no longer applicable. + JmeIosGLES.glShaderSource( + id, source.getDefines() + + source.getSource()); + } else { + String precision =""; + if (source.getType() == ShaderType.Fragment) { + precision = "precision mediump float;\n"; + } + JmeIosGLES.glShaderSource( + id, + precision + +source.getDefines() + + source.getSource()); + } +// int range[] = new int[2]; +// int precision[] = new int[1]; +// GLES20.glGetShaderPrecisionFormat(GLES20.GL_VERTEX_SHADER, GLES20.GL_HIGH_FLOAT, range, 0, precision, 0); +// System.out.println("PRECISION HIGH FLOAT VERTEX"); +// System.out.println("range "+range[0]+"," +range[1]); +// System.out.println("precision "+precision[0]); + + JmeIosGLES.glCompileShader(id); + JmeIosGLES.checkGLError(); + + JmeIosGLES.glGetShaderiv(id, JmeIosGLES.GL_COMPILE_STATUS, intBuf1, 0); + JmeIosGLES.checkGLError(); + + boolean compiledOK = intBuf1[0] == JmeIosGLES.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !compiledOK) { + // even if compile succeeded, check + // log for warnings + JmeIosGLES.glGetShaderiv(id, JmeIosGLES.GL_INFO_LOG_LENGTH, intBuf1, 0); + JmeIosGLES.checkGLError(); + infoLog = JmeIosGLES.glGetShaderInfoLog(id); + } + + if (compiledOK) { + if (infoLog != null) { + logger.log(Level.FINE, "compile success: {0}, {1}", new Object[]{source.getName(), infoLog}); + } else { + logger.log(Level.FINE, "compile success: {0}", source.getName()); + } + source.clearUpdateNeeded(); + } else { + logger.log(Level.WARNING, "Bad compile of:\n{0}", + new Object[]{ShaderDebug.formatShaderSource(source.getDefines(), source.getSource(),stringBuf.toString())}); + if (infoLog != null) { + throw new RendererException("compile error in:" + source + " error:" + infoLog); + } else { + throw new RendererException("compile error in:" + source + " error: "); + } + } + } + + + public int convertShaderType(ShaderType type) { + switch (type) { + case Fragment: + return JmeIosGLES.GL_FRAGMENT_SHADER; + case Vertex: + return JmeIosGLES.GL_VERTEX_SHADER; +// case Geometry: +// return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB; + default: + throw new RuntimeException("Unrecognized shader type."); + } + } + + private int convertTestFunction(RenderState.TestFunction testFunc) { + switch (testFunc) { + case Never: + return JmeIosGLES.GL_NEVER; + case Less: + return JmeIosGLES.GL_LESS; + case LessOrEqual: + return JmeIosGLES.GL_LEQUAL; + case Greater: + return JmeIosGLES.GL_GREATER; + case GreaterOrEqual: + return JmeIosGLES.GL_GEQUAL; + case Equal: + return JmeIosGLES.GL_EQUAL; + case NotEqual: + return JmeIosGLES.GL_NOTEQUAL; + case Always: + return JmeIosGLES.GL_ALWAYS; + default: + throw new UnsupportedOperationException("Unrecognized test function: " + testFunc); + } + } +} \ No newline at end of file diff --git a/engine/src/ios/com/jme3/renderer/ios/JmeIosGLES.java b/engine/src/ios/com/jme3/renderer/ios/JmeIosGLES.java new file mode 100644 index 000000000..0811dc422 --- /dev/null +++ b/engine/src/ios/com/jme3/renderer/ios/JmeIosGLES.java @@ -0,0 +1,273 @@ +package com.jme3.renderer.ios; + +import com.jme3.renderer.RendererException; + +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The iOS GLES interface iOS alternative to Android's GLES20 class + * + * @author Kostyantyn Hushchyn + */ +public class JmeIosGLES { + private static final Logger logger = Logger.getLogger(JmeIosGLES.class.getName()); + + private static boolean ENABLE_ERROR_CHECKING = true; + + public static final int GL_ALPHA = 0x00001906; + public static final int GL_ALWAYS = 0x00000207; + public static final int GL_ARRAY_BUFFER = 0x00008892; + public static final int GL_BACK = 0x00000405; + public static final int GL_BLEND = 0x00000be2; + public static final int GL_BYTE = 0x00001400; + public static final int GL_CLAMP_TO_EDGE = 0x0000812f; + public static final int GL_COLOR_ATTACHMENT0 = 0x00008ce0; + public static final int GL_COLOR_BUFFER_BIT = 0x00004000; + public static final int GL_COMPILE_STATUS = 0x00008b81; + public static final int GL_COMPRESSED_TEXTURE_FORMATS = 0x000086a3; + public static final int GL_CULL_FACE = 0x00000b44; + public static final int GL_DEPTH_ATTACHMENT = 0x00008d00; + public static final int GL_DEPTH_BUFFER_BIT = 0x00000100; + public static final int GL_DEPTH_COMPONENT = 0x00001902; + public static final int GL_DEPTH_COMPONENT16 = 0x000081a5; + public static final int GL_DEPTH_TEST = 0x00000b71; + public static final int GL_DITHER = 0x00000bd0; + public static final int GL_DST_COLOR = 0x00000306; + public static final int GL_DYNAMIC_DRAW = 0x000088e8; + public static final int GL_EQUAL = 0x00000202; + public static final int GL_ELEMENT_ARRAY_BUFFER = 0x00008893; + public static final int GL_EXTENSIONS = 0x00001f03; + public static final int GL_FALSE = 0x00000000; + public static final int GL_FLOAT = 0x00001406; + public static final int GL_FRAGMENT_SHADER = 0x00008b30; + public static final int GL_FRAMEBUFFER = 0x00008d40; + public static final int GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x00008cd1; + public static final int GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x00008cd0; + public static final int GL_FRAMEBUFFER_COMPLETE = 0x00008cd5; + public static final int GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x00008cd6; + public static final int GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x00008cd9; + public static final int GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x00008cd7; + public static final int GL_FRAMEBUFFER_UNSUPPORTED = 0x00008cdd; + public static final int GL_FRONT = 0x00000404; + public static final int GL_FRONT_AND_BACK = 0x00000408; + public static final int GL_GEQUAL = 0x00000206; + public static final int GL_GREATER = 0x00000204; + public static final int GL_HIGH_FLOAT = 0x00008df2; + public static final int GL_INFO_LOG_LENGTH = 0x00008b84; + public static final int GL_INT = 0x00001404; + public static final int GL_LEQUAL = 0x00000203; + public static final int GL_LESS = 0x00000201; + public static final int GL_LINEAR = 0x00002601; + public static final int GL_LINEAR_MIPMAP_LINEAR = 0x00002703; + public static final int GL_LINEAR_MIPMAP_NEAREST = 0x00002701; + public static final int GL_LINES = 0x00000001; + public static final int GL_LINE_LOOP = 0x00000002; + public static final int GL_LINE_STRIP = 0x00000003; + public static final int GL_LINK_STATUS = 0x00008b82; + public static final int GL_LUMINANCE = 0x00001909; + public static final int GL_LUMINANCE_ALPHA = 0x0000190a; + public static final int GL_MAX_CUBE_MAP_TEXTURE_SIZE = 0x0000851c; + public static final int GL_MAX_FRAGMENT_UNIFORM_VECTORS = 0x00008dfd; + public static final int GL_MAX_RENDERBUFFER_SIZE = 0x000084e8; + public static final int GL_MAX_TEXTURE_IMAGE_UNITS = 0x00008872; + public static final int GL_MAX_TEXTURE_SIZE = 0x00000d33; + public static final int GL_MAX_VARYING_VECTORS = 0x00008dfc; + public static final int GL_MAX_VERTEX_ATTRIBS = 0x00008869; + public static final int GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x00008b4c; + public static final int GL_MAX_VERTEX_UNIFORM_VECTORS = 0x00008dfb; + public static final int GL_MIRRORED_REPEAT = 0x00008370; + public static final int GL_NEAREST = 0x00002600; + public static final int GL_NEAREST_MIPMAP_LINEAR = 0x00002702; + public static final int GL_NEAREST_MIPMAP_NEAREST = 0x00002700; + public static final int GL_NEVER = 0x00000200; + public static final int GL_NONE = 0x00000000; + public static final int GL_NOTEQUAL = 0x00000205; + public static final int GL_NUM_COMPRESSED_TEXTURE_FORMATS = 0x000086a2; + public static final int GL_ONE = 0x00000001; + public static final int GL_ONE_MINUS_SRC_ALPHA = 0x00000303; + public static final int GL_ONE_MINUS_SRC_COLOR = 0x00000301; + public static final int GL_POINTS = 0x00000000; + public static final int GL_POLYGON_OFFSET_FILL = 0x00008037; + public static final int GL_RENDERBUFFER = 0x00008d41; + public static final int GL_RENDERER = 0x00001f01; + public static final int GL_REPEAT = 0x00002901; + public static final int GL_RGB = 0x00001907; + public static final int GL_RGB565 = 0x00008d62; + public static final int GL_RGB5_A1 = 0x00008057; + public static final int GL_RGBA = 0x00001908; + public static final int GL_RGBA4 = 0x00008056; + public static final int GL_SAMPLE_ALPHA_TO_COVERAGE = 0x0000809e; + public static final int GL_SCISSOR_TEST = 0x00000c11; + public static final int GL_SHADING_LANGUAGE_VERSION = 0x00008b8c; + public static final int GL_SHORT = 0x00001402; + public static final int GL_SRC_COLOR = 0x00000300; + public static final int GL_SRC_ALPHA = 0x00000302; + public static final int GL_STATIC_DRAW = 0x000088e4; + public static final int GL_STENCIL_BUFFER_BIT = 0x00000400; + public static final int GL_STREAM_DRAW = 0x000088e0; + public static final int GL_SUBPIXEL_BITS = 0x00000d50; + public static final int GL_TEXTURE = 0x00001702; + public static final int GL_TEXTURE0 = 0x000084c0; + public static final int GL_TEXTURE_2D = 0x00000de1; + public static final int GL_TEXTURE_CUBE_MAP = 0x00008513; + public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x00008515; + public static final int GL_TEXTURE_MAG_FILTER = 0x00002800; + public static final int GL_TEXTURE_MIN_FILTER = 0x00002801; + public static final int GL_TEXTURE_WRAP_S = 0x00002802; + public static final int GL_TEXTURE_WRAP_T = 0x00002803; + public static final int GL_TRIANGLES = 0x00000004; + public static final int GL_TRIANGLE_FAN = 0x00000006; + public static final int GL_TRIANGLE_STRIP = 0x00000005; + public static final int GL_TRUE = 0x00000001; + public static final int GL_UNPACK_ALIGNMENT = 0x00000cf5; + public static final int GL_UNSIGNED_BYTE = 0x00001401; + public static final int GL_UNSIGNED_INT = 0x00001405; + public static final int GL_UNSIGNED_SHORT = 0x00001403; + public static final int GL_UNSIGNED_SHORT_4_4_4_4 = 0x00008033; + public static final int GL_UNSIGNED_SHORT_5_5_5_1 = 0x00008034; + public static final int GL_UNSIGNED_SHORT_5_6_5 = 0x00008363; + public static final int GL_VENDOR = 0x00001f00; + public static final int GL_VERSION = 0x00001f02; + public static final int GL_VERTEX_SHADER = 0x00008b31; + public static final int GL_ZERO = 0x00000000; + + public static native void glActiveTexture(int texture); + public static native void glAttachShader(int program, int shader); + public static native void glBindBuffer(int target, int buffer); + public static native void glBindFramebuffer(int target, int framebuffer); + public static native void glBindRenderbuffer(int target, int renderbuffer); + public static native void glBindTexture(int target, int texture); +// public static native void glBindVertexArray // TODO: Investigate this + public static native void glBlendFunc(int sfactor, int dfactor); + public static native void glBufferData(int target, int size, Buffer data, int usage); + public static native void glBufferData2(int target, int size, byte[] data, int offset, int usage); + public static native void glBufferSubData(int target, int offset, int size, Buffer data); + public static native void glBufferSubData2(int target, int offset, int size, byte[] data, int dataoffset); + public static native int glCheckFramebufferStatus(int target); + public static native void glClear(int mask); + public static native void glClearColor(float red, float green, float blue, float alpha); + public static native void glColorMask(boolean red, boolean green, boolean blue, boolean alpha); + public static native void glCompileShader(int shader); + public static native void glCompressedTexImage2D(int target, int level, int internalformat, int width, int height, int border, int imageSize, Buffer pixels); + public static native void glCompressedTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int imageSize, Buffer pixels); + public static native int glCreateProgram(); + public static native int glCreateShader(int type); + public static native void glCullFace(int mode); + public static native void glDeleteBuffers(int n, int[] buffers, int offset); + public static native void glDeleteFramebuffers(int n, int[] framebuffers, int offset); + public static native void glDeleteProgram(int program); + public static native void glDeleteRenderbuffers(int n, int[] renderbuffers, int offset); + public static native void glDeleteShader(int shader); + public static native void glDeleteTextures(int n, int[] textures, int offset); + public static native void glDepthFunc(int func); + public static native void glDepthMask(boolean flag); + public static native void glDepthRangef(float zNear, float zFar); + public static native void glDetachShader(int program, int shader); + public static native void glDisableVertexAttribArray(int index); + public static native void glDisable(int cap); + public static native void glDrawArrays(int mode, int first, int count); + public static native void glDrawElements(int mode, int count, int type, Buffer indices); + public static native void glDrawElements2(int mode, int count, int type, byte[] indices, int offset); + public static native void glDrawElementsIndex(int mode, int count, int type, int offset); + public static native void glEnable(int cap); + public static native void glEnableVertexAttribArray(int index); + public static native void glFramebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer); + public static native void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level); + public static native void glGenBuffers(int n, int[] buffers, int offset); + public static native void glGenFramebuffers(int n, int[] framebuffers, int offset); + public static native void glGenRenderbuffers(int n, int[] renderbuffers, int offset); + public static native void glGenTextures(int n, int[] textures, int offset); + public static native void glGenerateMipmap(int target); + public static native int glGetAttribLocation(int program, String name); + public static native int glGetError(); + public static native void glGetFramebufferAttachmentParameteriv(int target, int attachment, int pname, int[] params, int offset); + public static native void glGetIntegerv (int pname, int[] params, int offset); + public static native String glGetProgramInfoLog(int program); + public static native void glGetProgramiv(int program, int pname, int[] params, int offset); + public static native String glGetShaderInfoLog(int shader); + public static native void glGetShaderiv(int shader, int pname, int[] params, int offset); + public static native String glGetString(int name); + public static native int glGetUniformLocation(int program, String name); + public static native boolean glIsFramebuffer(int framebuffer); + public static native boolean glIsRenderbuffer(int renderbuffer); + public static native void glLineWidth(float width); + public static native void glLinkProgram(int program); + public static native void glPixelStorei(int pname, int param); + public static native void glPolygonOffset(float factor, float units); + public static native void glReadPixels(int vpX, int vpY, int vpW, int vpH, int format, int type, Buffer pixels); + public static native void glReadPixels2(int vpX, int vpY, int vpW, int vpH, int format, int type, byte[] pixels, int offset, int size); + public static native void glRenderbufferStorage(int target, int internalformat, int width, int height); + public static native void glScissor(int x, int y, int width, int height); + public static native void glShaderSource(int shader, String string); + public static native void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, Buffer pixels); + public static native void glTexParameteri(int target, int pname, int param); + public static native void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, Buffer pixels); + public static native void glUniform1f(int location, float x); + public static native void glUniform1fv(int location, int count, FloatBuffer v); + public static native void glUniform1fv2(int location, int count, float[] v, int offset); + public static native void glUniform1i(int location, int x); + public static native void glUniform1iv(int location, int count, IntBuffer v); + public static native void glUniform1iv2(int location, int count, int[] v, int offset); + public static native void glUniform2f(int location, float x, float y); + public static native void glUniform2fv(int location, int count, FloatBuffer v); + public static native void glUniform2fv2(int location, int count, float[] v, int offset); + public static native void glUniform3f(int location, float x, float y, float z); + public static native void glUniform3fv(int location, int count, FloatBuffer v); + public static native void glUniform3fv2(int location, int count, float[] v, int offset); + public static native void glUniform4f(int location, float x, float y, float z, float w); + public static native void glUniform4fv(int location, int count, FloatBuffer v); + public static native void glUniform4fv2(int location, int count, float[] v, int offset); + public static native void glUniformMatrix3fv(int location, int count, boolean transpose, FloatBuffer value); + public static native void glUniformMatrix3fv2(int location, int count, boolean transpose, float[] value, int offset); + public static native void glUniformMatrix4fv(int location, int count, boolean transpose, FloatBuffer value); + public static native void glUniformMatrix4fv2(int location, int count, boolean transpose, float[] value, int offset); + public static native void glUseProgram(int program); + //public static native void glVertexAttribPointer(int indx, int size, int type, boolean normalized, int stride, byte[] ptr, int offset); + public static native void glVertexAttribPointer(int indx, int size, int type, boolean normalized, int stride, Buffer ptr); + public static native void glVertexAttribPointer2(int indx, int size, int type, boolean normalized, int stride, int offset); + public static native void glViewport(int x, int y, int width, int height); + + + public static void checkGLError() { + if (!ENABLE_ERROR_CHECKING) { + return; + } + int error = glGetError(); + if (error != 0) { + String message = null;//GLU.gluErrorString(error); + if (message == null) { + throw new RendererException("An unknown [" + error + "] OpenGL error has occurred."); + } else { + throw new RendererException("An OpenGL error has occurred: " + message); + } + } + } + + /* + public static String gluErrorString(int error) { + switch (error) { + case GL10.GL_NO_ERROR: + return "no error"; + case GL10.GL_INVALID_ENUM: + return "invalid enum"; + case GL10.GL_INVALID_VALUE: + return "invalid value"; + case GL10.GL_INVALID_OPERATION: + return "invalid operation"; + case GL10.GL_STACK_OVERFLOW: + return "stack overflow"; + case GL10.GL_STACK_UNDERFLOW: + return "stack underflow"; + case GL10.GL_OUT_OF_MEMORY: + return "out of memory"; + default: + return null; + } + } + */ + +} \ No newline at end of file diff --git a/engine/src/ios/com/jme3/renderer/ios/TextureUtil.java b/engine/src/ios/com/jme3/renderer/ios/TextureUtil.java new file mode 100644 index 000000000..596df0ce4 --- /dev/null +++ b/engine/src/ios/com/jme3/renderer/ios/TextureUtil.java @@ -0,0 +1,591 @@ +package com.jme3.renderer.ios; + +//import android.graphics.Bitmap; +//import android.opengl.ETC1; +//import android.opengl.ETC1Util.ETC1Texture; +//import android.opengl.JmeIosGLES; +//import android.opengl.GLUtils; +//import com.jme3.asset.AndroidImageInfo; +import com.jme3.renderer.ios.JmeIosGLES; +import com.jme3.math.FastMath; +import com.jme3.renderer.RendererException; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TextureUtil { + + private static final Logger logger = Logger.getLogger(TextureUtil.class.getName()); + //TODO Make this configurable through appSettings + public static boolean ENABLE_COMPRESSION = true; + private static boolean NPOT = false; + private static boolean ETC1support = false; + private static boolean DXT1 = false; + private static boolean PVRTC = false; + private static boolean DEPTH24_STENCIL8 = false; + private static boolean DEPTH_TEXTURE = false; + private static boolean RGBA8 = false; + + // Same constant used by both GL_ARM_rgba8 and GL_OES_rgb8_rgba8. + private static final int GL_RGBA8 = 0x8058; + + private static final int GL_DXT1 = 0x83F0; + private static final int GL_DXT1A = 0x83F1; + + private static final int GL_DEPTH_STENCIL_OES = 0x84F9; + private static final int GL_UNSIGNED_INT_24_8_OES = 0x84FA; + private static final int GL_DEPTH24_STENCIL8_OES = 0x88F0; + + public static void loadTextureFeatures(String extensionString) { + ETC1support = extensionString.contains("GL_OES_compressed_ETC1_RGB8_texture"); + DEPTH24_STENCIL8 = extensionString.contains("GL_OES_packed_depth_stencil"); + NPOT = extensionString.contains("GL_IMG_texture_npot") + || extensionString.contains("GL_OES_texture_npot") + || extensionString.contains("GL_NV_texture_npot_2D_mipmap"); + + PVRTC = extensionString.contains("GL_IMG_texture_compression_pvrtc"); + + DXT1 = extensionString.contains("GL_EXT_texture_compression_dxt1"); + DEPTH_TEXTURE = extensionString.contains("GL_OES_depth_texture"); + + RGBA8 = extensionString.contains("GL_ARM_rgba8") || + extensionString.contains("GL_OES_rgb8_rgba8"); + + logger.log(Level.FINE, "Supports ETC1? {0}", ETC1support); + logger.log(Level.FINE, "Supports DEPTH24_STENCIL8? {0}", DEPTH24_STENCIL8); + logger.log(Level.FINE, "Supports NPOT? {0}", NPOT); + logger.log(Level.FINE, "Supports PVRTC? {0}", PVRTC); + logger.log(Level.FINE, "Supports DXT1? {0}", DXT1); + logger.log(Level.FINE, "Supports DEPTH_TEXTURE? {0}", DEPTH_TEXTURE); + logger.log(Level.FINE, "Supports RGBA8? {0}", RGBA8); + } + + /* + private static void buildMipmap(Bitmap bitmap, boolean compress) { + int level = 0; + int height = bitmap.getHeight(); + int width = bitmap.getWidth(); + + logger.log(Level.FINEST, " - Generating mipmaps for bitmap using SOFTWARE"); + + JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1); + + while (height >= 1 || width >= 1) { + //First of all, generate the texture from our bitmap and set it to the according level + if (compress) { + logger.log(Level.FINEST, " - Uploading LOD level {0} ({1}x{2}) with compression.", new Object[]{level, width, height}); + uploadBitmapAsCompressed(JmeIosGLES.GL_TEXTURE_2D, level, bitmap, false, 0, 0); + } else { + logger.log(Level.FINEST, " - Uploading LOD level {0} ({1}x{2}) directly.", new Object[]{level, width, height}); + GLUtils.texImage2D(JmeIosGLES.GL_TEXTURE_2D, level, bitmap, 0); + } + + if (height == 1 || width == 1) { + break; + } + + //Increase the mipmap level + height /= 2; + width /= 2; + Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true); + + // Recycle any bitmaps created as a result of scaling the bitmap. + // Do not recycle the original image (mipmap level 0) + if (level != 0) { + bitmap.recycle(); + } + + bitmap = bitmap2; + + level++; + } + } + + private static void uploadBitmapAsCompressed(int target, int level, Bitmap bitmap, boolean subTexture, int x, int y) { + if (bitmap.hasAlpha()) { + logger.log(Level.FINEST, " - Uploading bitmap directly. Cannot compress as alpha present."); + if (subTexture) { + GLUtils.texSubImage2D(target, level, x, y, bitmap); + JmeIosGLES.checkGLError(); + } else { + GLUtils.texImage2D(target, level, bitmap, 0); + JmeIosGLES.checkGLError(); + } + } else { + // Convert to RGB565 + int bytesPerPixel = 2; + Bitmap rgb565 = bitmap.copy(Bitmap.Config.RGB_565, true); + + // Put texture data into ByteBuffer + ByteBuffer inputImage = BufferUtils.createByteBuffer(bitmap.getRowBytes() * bitmap.getHeight()); + rgb565.copyPixelsToBuffer(inputImage); + inputImage.position(0); + + // Delete the copied RGB565 image + rgb565.recycle(); + + // Encode the image into the output bytebuffer + int encodedImageSize = ETC1.getEncodedDataSize(bitmap.getWidth(), bitmap.getHeight()); + ByteBuffer compressedImage = BufferUtils.createByteBuffer(encodedImageSize); + ETC1.encodeImage(inputImage, bitmap.getWidth(), + bitmap.getHeight(), + bytesPerPixel, + bytesPerPixel * bitmap.getWidth(), + compressedImage); + + // Delete the input image buffer + BufferUtils.destroyDirectBuffer(inputImage); + + // Create an ETC1Texture from the compressed image data + ETC1Texture etc1tex = new ETC1Texture(bitmap.getWidth(), bitmap.getHeight(), compressedImage); + + // Upload the ETC1Texture + if (bytesPerPixel == 2) { + int oldSize = (bitmap.getRowBytes() * bitmap.getHeight()); + int newSize = compressedImage.capacity(); + logger.log(Level.FINEST, " - Uploading compressed image to GL, oldSize = {0}, newSize = {1}, ratio = {2}", new Object[]{oldSize, newSize, (float) oldSize / newSize}); + if (subTexture) { + JmeIosGLES.glCompressedTexSubImage2D(target, + level, + x, y, + bitmap.getWidth(), + bitmap.getHeight(), + ETC1.ETC1_RGB8_OES, + etc1tex.getData().capacity(), + etc1tex.getData()); + + JmeIosGLES.checkGLError(); + } else { + JmeIosGLES.glCompressedTexImage2D(target, + level, + ETC1.ETC1_RGB8_OES, + bitmap.getWidth(), + bitmap.getHeight(), + 0, + etc1tex.getData().capacity(), + etc1tex.getData()); + + JmeIosGLES.checkGLError(); + } + +// ETC1Util.loadTexture(target, level, 0, JmeIosGLES.GL_RGB, +// JmeIosGLES.GL_UNSIGNED_SHORT_5_6_5, etc1Texture); +// } else if (bytesPerPixel == 3) { +// ETC1Util.loadTexture(target, level, 0, JmeIosGLES.GL_RGB, +// JmeIosGLES.GL_UNSIGNED_BYTE, etc1Texture); + } + + BufferUtils.destroyDirectBuffer(compressedImage); + } + } + + /** + * uploadTextureBitmap uploads a native android bitmap + */ + /* + public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips) { + uploadTextureBitmap(target, bitmap, needMips, false, 0, 0); + } + + /** + * uploadTextureBitmap uploads a native android bitmap + */ + /* + public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips, boolean subTexture, int x, int y) { + boolean recycleBitmap = false; + //TODO, maybe this should raise an exception when NPOT is not supported + + boolean willCompress = ENABLE_COMPRESSION && ETC1support && !bitmap.hasAlpha(); + if (needMips && willCompress) { + // Image is compressed and mipmaps are desired, generate them + // using software. + buildMipmap(bitmap, willCompress); + } else { + if (willCompress) { + // Image is compressed but mipmaps are not desired, upload directly. + logger.log(Level.FINEST, " - Uploading compressed bitmap. Mipmaps are not generated."); + uploadBitmapAsCompressed(target, 0, bitmap, subTexture, x, y); + + } else { + // Image is not compressed, mipmaps may or may not be desired. + logger.log(Level.FINEST, " - Uploading bitmap directly.{0}", + (needMips + ? " Mipmaps will be generated in HARDWARE" + : " Mipmaps are not generated.")); + if (subTexture) { + System.err.println("x : " + x + " y :" + y + " , " + bitmap.getWidth() + "/" + bitmap.getHeight()); + GLUtils.texSubImage2D(target, 0, x, y, bitmap); + JmeIosGLES.checkGLError(); + } else { + GLUtils.texImage2D(target, 0, bitmap, 0); + JmeIosGLES.checkGLError(); + } + + if (needMips) { + // No pregenerated mips available, + // generate from base level if required + JmeIosGLES.glGenerateMipmap(target); + JmeIosGLES.checkGLError(); + } + } + } + + if (recycleBitmap) { + bitmap.recycle(); + } + } + */ + + public static void uploadTextureAny(Image img, int target, int index, boolean needMips) { + /* + if (img.getEfficentData() instanceof AndroidImageInfo) { + logger.log(Level.FINEST, " === Uploading image {0}. Using BITMAP PATH === ", img); + // If image was loaded from asset manager, use fast path + AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData(); + uploadTextureBitmap(target, imageInfo.getBitmap(), needMips); + } else { + */ + logger.log(Level.FINEST, " === Uploading image {0}. Using BUFFER PATH === ", img); + boolean wantGeneratedMips = needMips && !img.hasMipmaps(); + if (wantGeneratedMips && img.getFormat().isCompressed()) { + logger.log(Level.WARNING, "Generating mipmaps is only" + + " supported for Bitmap based or non-compressed images!"); + } + + // Upload using slower path + logger.log(Level.FINEST, " - Uploading bitmap directly.{0}", + (wantGeneratedMips + ? " Mipmaps will be generated in HARDWARE" + : " Mipmaps are not generated.")); + + uploadTexture(img, target, index); + + // Image was uploaded using slower path, since it is not compressed, + // then compress it + if (wantGeneratedMips) { + // No pregenerated mips available, + // generate from base level if required + JmeIosGLES.glGenerateMipmap(target); + JmeIosGLES.checkGLError(); + } + //} + } + + private static void unsupportedFormat(Format fmt) { + throw new UnsupportedOperationException("The image format '" + fmt + "' is unsupported by the video hardware."); + } + + public static IosGLImageFormat getImageFormat(Format fmt) throws UnsupportedOperationException { + IosGLImageFormat imageFormat = new IosGLImageFormat(); + switch (fmt) { + case RGBA16: + case RGB16: + case RGB10: + case Luminance16: + case Luminance16Alpha16: + case Alpha16: + case Depth32: + case Depth32F: + throw new UnsupportedOperationException("The image format '" + + fmt + "' is not supported by OpenGL ES 2.0 specification."); + case Alpha8: + imageFormat.format = JmeIosGLES.GL_ALPHA; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + // Highest precision alpha supported by vanilla OGLES2 + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4; + } + break; + case Luminance8: + imageFormat.format = JmeIosGLES.GL_LUMINANCE; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + // Highest precision luminance supported by vanilla OGLES2 + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; + } + break; + case Luminance8Alpha8: + imageFormat.format = JmeIosGLES.GL_LUMINANCE_ALPHA; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4; + } + break; + case RGB565: + imageFormat.format = JmeIosGLES.GL_RGB; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT_5_6_5; + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; + break; + case ARGB4444: + imageFormat.format = JmeIosGLES.GL_RGBA4; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT_4_4_4_4; + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4; + break; + case RGB5A1: + imageFormat.format = JmeIosGLES.GL_RGBA; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT_5_5_5_1; + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB5_A1; + break; + case RGB8: + imageFormat.format = JmeIosGLES.GL_RGB; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + // Fallback: Use RGB565 if RGBA8 is not available. + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; + } + break; + case BGR8: + imageFormat.format = JmeIosGLES.GL_RGB; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; + } + break; + case RGBA8: + imageFormat.format = JmeIosGLES.GL_RGBA; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4; + } + break; + case Depth: + case Depth16: + if (!DEPTH_TEXTURE) { + unsupportedFormat(fmt); + } + imageFormat.format = JmeIosGLES.GL_DEPTH_COMPONENT; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT; + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_DEPTH_COMPONENT16; + break; + case Depth24: + case Depth24Stencil8: + if (!DEPTH_TEXTURE) { + unsupportedFormat(fmt); + } + if (DEPTH24_STENCIL8) { + // NEW: True Depth24 + Stencil8 format. + imageFormat.format = GL_DEPTH_STENCIL_OES; + imageFormat.dataType = GL_UNSIGNED_INT_24_8_OES; + imageFormat.renderBufferStorageFormat = GL_DEPTH24_STENCIL8_OES; + } else { + // Vanilla OGLES2, only Depth16 available. + imageFormat.format = JmeIosGLES.GL_DEPTH_COMPONENT; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT; + imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_DEPTH_COMPONENT16; + } + break; + case DXT1: + if (!DXT1) { + unsupportedFormat(fmt); + } + imageFormat.format = GL_DXT1; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; + imageFormat.compress = true; + break; + case DXT1A: + if (!DXT1) { + unsupportedFormat(fmt); + } + imageFormat.format = GL_DXT1A; + imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; + imageFormat.compress = true; + break; + default: + throw new UnsupportedOperationException("Unrecognized format: " + fmt); + } + return imageFormat; + } + + public static class IosGLImageFormat { + + boolean compress = false; + int format = -1; + int renderBufferStorageFormat = -1; + int dataType = -1; + } + + private static void uploadTexture(Image img, + int target, + int index) { + + /* + if (img.getEfficentData() instanceof AndroidImageInfo) { + throw new RendererException("This image uses efficient data. " + + "Use uploadTextureBitmap instead."); + } + */ + + // 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(); + + if (!NPOT) { + // Check if texture is POT + if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(height)) { + throw new RendererException("Non-power-of-2 textures " + + "are not supported by the video hardware " + + "and no scaling path available for image: " + img); + } + } + IosGLImageFormat imageFormat = getImageFormat(fmt); + + if (data != null) { + JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1); + JmeIosGLES.checkGLError(); + } + + 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}; + } + } + + for (int i = 0; i < mipSizes.length; i++) { + int mipWidth = Math.max(1, width >> i); + int mipHeight = Math.max(1, height >> i); + + if (data != null) { + data.position(pos); + data.limit(pos + mipSizes[i]); + } + + if (imageFormat.compress && data != null) { + JmeIosGLES.glCompressedTexImage2D(target, + i, + imageFormat.format, + mipWidth, + mipHeight, + 0, + data.remaining(), + data); + } else { + JmeIosGLES.glTexImage2D(target, + i, + imageFormat.format, + mipWidth, + mipHeight, + 0, + imageFormat.format, + imageFormat.dataType, + data); + } + JmeIosGLES.checkGLError(); + + pos += mipSizes[i]; + } + } + + /** + * Update the texture currently bound to target at with data from the given + * Image at position x and y. The parameter index is used as the zoffset in + * case a 3d texture or texture 2d array is being updated. + * + * @param image Image with the source data (this data will be put into the + * texture) + * @param target the target texture + * @param index the mipmap level to update + * @param x the x position where to put the image in the texture + * @param y the y position where to put the image in the texture + */ + public static void uploadSubTexture( + Image img, + int target, + int index, + int x, + int y) { + //TODO: + /* + if (img.getEfficentData() instanceof AndroidImageInfo) { + AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData(); + uploadTextureBitmap(target, imageInfo.getBitmap(), true, true, x, y); + 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(); + + if (!NPOT) { + // Check if texture is POT + if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(height)) { + throw new RendererException("Non-power-of-2 textures " + + "are not supported by the video hardware " + + "and no scaling path available for image: " + img); + } + } + IosGLImageFormat imageFormat = getImageFormat(fmt); + + if (data != null) { + JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1); + JmeIosGLES.checkGLError(); + } + + 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}; + } + } + + for (int i = 0; i < mipSizes.length; i++) { + int mipWidth = Math.max(1, width >> i); + int mipHeight = Math.max(1, height >> i); + + if (data != null) { + data.position(pos); + data.limit(pos + mipSizes[i]); + } + + if (imageFormat.compress && data != null) { + JmeIosGLES.glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, data.remaining(), data); + JmeIosGLES.checkGLError(); + } else { + JmeIosGLES.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, imageFormat.dataType, data); + JmeIosGLES.checkGLError(); + } + + pos += mipSizes[i]; + } + } +}