diff --git a/android/core/src/processing/opengl/FontTexture.java b/android/core/src/processing/opengl/FontTexture.java index df8115fc2..0445d611a 100644 --- a/android/core/src/processing/opengl/FontTexture.java +++ b/android/core/src/processing/opengl/FontTexture.java @@ -159,14 +159,14 @@ class FontTexture implements PConstants { currentTex = textures.length - 1; PImage[] tempImg = images; - images = new PImage[textures.length + 1]; + images = new PImage[textures.length]; PApplet.arrayCopy(tempImg, images, tempImg.length); images[tempImg.length] = pg.wrapTexture(tex); } lastTex = currentTex; // Make sure that the current texture is bound. - //tex.bind(); + tex.bind(); return resize; } @@ -324,14 +324,6 @@ class FontTexture implements PConstants { } } - if (lastTex == -1) { - lastTex = 0; - } - - if (currentTex != lastTex || resized) { - currentTex = idx; - } - TextureInfo tinfo = new TextureInfo(currentTex, offsetX, offsetY, w, h, rgba); offsetX += w; diff --git a/android/core/src/processing/opengl/FrameBuffer.java b/android/core/src/processing/opengl/FrameBuffer.java index 253c61aa9..74d9c5292 100644 --- a/android/core/src/processing/opengl/FrameBuffer.java +++ b/android/core/src/processing/opengl/FrameBuffer.java @@ -183,12 +183,14 @@ public class FrameBuffer implements PConstants { pg.popFramebuffer(); } - public void copy(FrameBuffer dest) { + public void copy(FrameBuffer dest, FrameBuffer current) { pgl.bindFramebuffer(PGL.READ_FRAMEBUFFER, this.glFbo); pgl.bindFramebuffer(PGL.DRAW_FRAMEBUFFER, dest.glFbo); pgl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, dest.width, dest.height, PGL.COLOR_BUFFER_BIT, PGL.NEAREST); + pgl.bindFramebuffer(PGL.READ_FRAMEBUFFER, current.glFbo); + pgl.bindFramebuffer(PGL.DRAW_FRAMEBUFFER, current.glFbo); } public void bind() { @@ -311,6 +313,24 @@ public class FrameBuffer implements PConstants { } + public int getDefaultReadBuffer() { + if (screenFb) { + return pgl.getDefaultReadBuffer(); + } else { + return PGL.COLOR_ATTACHMENT0; + } + } + + + public int getDefaultDrawBuffer() { + if (screenFb) { + return pgl.getDefaultDrawBuffer(); + } else { + return PGL.COLOR_ATTACHMENT0; + } + } + + /////////////////////////////////////////////////////////// // Allocate/release framebuffer. diff --git a/android/core/src/processing/opengl/PGL.java b/android/core/src/processing/opengl/PGL.java index d043f6df9..ae258012e 100644 --- a/android/core/src/processing/opengl/PGL.java +++ b/android/core/src/processing/opengl/PGL.java @@ -316,13 +316,13 @@ public class PGL { protected static final int EGL_OPENGL_ES2_BIT = 0x0004; /** Basic GLES 1.0 interface */ - public GL10 gl; + public static GL10 gl; /** GLU interface **/ - public PGLU glu; + public static PGLU glu; /** The current opengl context */ - static public EGLContext context; + public static EGLContext context; /** The PGraphics object using this interface */ protected PGraphicsOpenGL pg; @@ -342,14 +342,13 @@ public class PGL { /////////////////////////////////////////////////////////// - // FBO for incremental drawing + // FBO layer - protected static final boolean FORCE_SCREEN_FBO = false; - protected boolean firstOnscreenFrame = true; + protected boolean usingFBOlayer = false; + protected int[] glColorFbo = { 0 }; + protected int[] glColorTex = { 0, 0 }; protected int fboWidth, fboHeight; protected int backTex, frontTex; - protected int[] glColorTex = { 0, 0 }; - protected int[] glColorFbo = { 0 }; /////////////////////////////////////////////////////////// @@ -405,7 +404,9 @@ public class PGL { public PGL(PGraphicsOpenGL pg) { this.pg = pg; renderer = new AndroidRenderer(); - glu = new PGLU(); + if (glu == null) { + glu = new PGLU(); + } initialized = false; } @@ -414,7 +415,7 @@ public class PGL { } - protected void initPrimarySurface(int antialias) { + protected void initSurface(int antialias) { // We do the initialization in updatePrimary() because // at the moment initPrimarySurface() gets called we // cannot rely on the GL surface being actually @@ -422,12 +423,7 @@ public class PGL { } - protected void initOffscreenSurface(PGL primary) { - initialized = true; - } - - - protected void updatePrimary() { + protected void update() { if (!initialized) { String ext = GLES20.glGetString(GLES20.GL_EXTENSIONS); if (-1 < ext.indexOf("texture_non_power_of_two")) { @@ -520,87 +516,139 @@ public class PGL { GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_STENCIL_BUFFER_BIT); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); - PGraphicsOpenGL.screenFramebuffer.glFbo = 0; + PGraphicsOpenGL.drawFramebuffer.glFbo = 0; - // Use this instead for the new code to be implemented later... -// PGraphicsOpenGL.screenFramebuffer.glFbo = glColorFbo[0]; - - backTex = 1; - frontTex = 0; + backTex = 0; + frontTex = 1; initialized = true; } } - protected void updateOffscreen(PGL primary) { - gl = primary.gl; - } - - - protected int primaryDrawBuffer() { - if (PGraphicsOpenGL.screenFramebuffer.glFbo != 0) { - return GLES20.GL_BACK; + protected int getReadFramebuffer() { + if (usingFBOlayer) { + return glColorFbo[0]; } else { - return GLES20.GL_COLOR_ATTACHMENT0; + return 0; } } -/* - protected boolean primaryIsDoubleBuffered() { - return PGraphicsOpenGL.screenFramebuffer.glFbo != 0; - } -*/ - protected boolean primaryIsFboBacked() { - return PGraphicsOpenGL.screenFramebuffer.glFbo != 0; + protected int getDrawFramebuffer() { + if (usingFBOlayer) { + return glColorFbo[0]; + } else { + return 0; + } } - protected int getFboTexTarget() { - return GLES20.GL_TEXTURE_2D; - } - - - protected int getFboTexName() { - return glColorTex[0]; - } - - - protected int getFboWidth() { - return fboWidth; + protected int getDefaultDrawBuffer() { + if (usingFBOlayer) { + return GLES20.GL_COLOR_ATTACHMENT0; + } else { + return GLES20.GL_BACK; + } } - protected int getFboHeight() { - return fboHeight; - } - - - protected void bindPrimaryColorFBO() { - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, glColorFbo[0]); - PGraphicsOpenGL.screenFramebuffer.glFbo = glColorFbo[0]; - - // Make the color buffer opaque so it doesn't show - // the background when drawn on top of another surface. - GLES20.glColorMask(false, false, false, true); - GLES20.glClearColor(0, 0, 0, 1); - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); - GLES20.glColorMask(true, true, true, true); + protected int getDefaultReadBuffer() { + if (usingFBOlayer) { + return GLES20.GL_COLOR_ATTACHMENT0; + } else { + return GLES20.GL_FRONT; + } } - protected void bindPrimaryMultiFBO() { - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, glColorFbo[0]); - PGraphicsOpenGL.screenFramebuffer.glFbo = glColorFbo[0]; + protected boolean isFBOBacked() { + return usingFBOlayer; } - protected void bindBackBufferTex() { + protected boolean isMultisampled() { + return false; } - protected void unbindBackBufferTex() { + protected int getDepthBits() { + int[] temp = {0}; + GLES20.glGetIntegerv(GLES20.GL_DEPTH_BITS, temp, 0); + return temp[0]; + } + + + protected int getStencilBits() { + int[] temp = {0}; + GLES20.glGetIntegerv(GLES20.GL_STENCIL_BITS, temp, 0); + return temp[0]; + } + + + protected Texture wrapBackTexture() { + Texture tex = new Texture(pg.parent); + tex.init(glColorTex[backTex], + GLES20.GL_TEXTURE_2D, GLES20.GL_RGBA, + fboWidth, fboHeight, + GLES20.GL_NEAREST, GLES20.GL_NEAREST, + GLES20.GL_CLAMP_TO_EDGE, GLES20.GL_CLAMP_TO_EDGE); + tex.invertedY(true); + tex.colorBufferOf(pg); + pg.setCache(pg, tex); + return tex; + } + + + protected Texture wrapFrontTexture() { + Texture tex = new Texture(pg.parent); + tex.init(glColorTex[frontTex], + GLES20.GL_TEXTURE_2D, GLES20.GL_RGBA, + fboWidth, fboHeight, + GLES20.GL_NEAREST, GLES20.GL_NEAREST, + GLES20.GL_CLAMP_TO_EDGE, GLES20.GL_CLAMP_TO_EDGE); + tex.invertedY(true); + tex.colorBufferOf(pg); + return tex; + } + + + int getBackTextureName() { + return glColorTex[backTex]; + + } + + + int getFrontTextureName() { + return glColorTex[frontTex]; + } + + + protected void bindFrontTexture() { + if (!texturingIsEnabled(GLES20.GL_TEXTURE_2D)) { + enableTexturing(GLES20.GL_TEXTURE_2D); + } + gl.glBindTexture(GLES20.GL_TEXTURE_2D, glColorTex[frontTex]); + } + + + protected void unbindFrontTexture() { + if (textureIsBound(GLES20.GL_TEXTURE_2D, glColorTex[frontTex])) { + // We don't want to unbind another texture + // that might be bound instead of this one. + if (!texturingIsEnabled(GLES20.GL_TEXTURE_2D)) { + enableTexturing(GLES20.GL_TEXTURE_2D); + gl.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + disableTexturing(GLES20.GL_TEXTURE_2D); + } else { + gl.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + } + } + } + + + protected void syncBackTexture() { + // Nothing to do because there is no MSAA in GLES20 } @@ -609,27 +657,29 @@ public class PGL { // Frame rendering - protected void beginOnscreenDraw(boolean clear) { - - // TODO: enable this implementation later (solves the flickering problem): - /* - if (glColorFbo[0] != 0) { + protected void beginDraw(boolean clear0) { + if (!clear0 && glColorFbo[0] != 0) { + // Bind the FBO and use the back texture to draw to. GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, glColorFbo[0]); GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, - glColorTex[frontTex], 0); - - PGraphicsOpenGL.screenFramebuffer.glFbo = glColorFbo[0]; + glColorTex[backTex], 0); + usingFBOlayer = true; + } else { + usingFBOlayer = false; } -*/ + + + + /* if (clear && !FORCE_SCREEN_FBO) { // Simplest scenario: clear mode means we clear both the color and depth // buffers. No need for saving front color buffer, etc. GLES20.glClearColor(0, 0, 0, 0); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); - PGraphicsOpenGL.screenFramebuffer.glFbo = 0; + PGraphicsOpenGL.drawFramebuffer.glFbo = 0; } else { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, glColorFbo[0]); GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, @@ -651,69 +701,69 @@ public class PGL { fboWidth, fboHeight, 0, 0, pg.width, pg.height, 0, 0, pg.width, pg.height); } - PGraphicsOpenGL.screenFramebuffer.glFbo = glColorFbo[0]; + PGraphicsOpenGL.drawFramebuffer.glFbo = glColorFbo[0]; } if (firstOnscreenFrame) { firstOnscreenFrame = false; } - +*/ } - protected void endOnscreenDraw(boolean clear0) { -/* - // TODO: enable this implementation later (solves the flickering problem): - if (glColorFbo[0] != 0) { - // We are in the primary surface, and no clear mode, this means that the - // current contents of the front buffer needs to be used in the next frame - // as the background. + protected void endDraw(boolean clear) { + if (usingFBOlayer) { + // Draw the contents of the back texture to the main framebuffer. GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); GLES20.glClearDepthf(1); GLES20.glClearColor(0, 0, 0, 0); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); - // Render current front texture to screen, without blending. + // Render current back texture to screen, without blending. GLES20.glDisable(GLES20.GL_BLEND); - drawTexture(GLES20.GL_TEXTURE_2D, glColorTex[frontTex], + drawTexture(GLES20.GL_TEXTURE_2D, glColorTex[backTex], fboWidth, fboHeight, 0, 0, pg.width, pg.height, 0, 0, pg.width, pg.height); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, glColorFbo[0]); - // Blitting the front texture into the back texture. - GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, - GLES20.GL_COLOR_ATTACHMENT0, - GLES20.GL_TEXTURE_2D, - glColorTex[backTex], 0); - drawTexture(GLES20.GL_TEXTURE_2D, glColorTex[frontTex], - fboWidth, fboHeight, - 0, 0, pg.width, pg.height, 0, 0, pg.width, pg.height); + if (!clear) { + // Draw the back texture into the front texture, which will be used as + // back texture in the next frame. Otherwise flickering will occur if + // the sketch uses "incremental drawing" (background() not called). + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, + GLES20.GL_COLOR_ATTACHMENT0, + GLES20.GL_TEXTURE_2D, + glColorTex[frontTex], 0); + drawTexture(GLES20.GL_TEXTURE_2D, glColorTex[backTex], + fboWidth, fboHeight, + 0, 0, pg.width, pg.height, 0, 0, pg.width, pg.height); - // Leave the front texture as current - GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, - GLES20.GL_COLOR_ATTACHMENT0, - GLES20.GL_TEXTURE_2D, - glColorTex[frontTex], 0); + // Leave the back texture as current + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, + GLES20.GL_COLOR_ATTACHMENT0, + GLES20.GL_TEXTURE_2D, + glColorTex[backTex], 0); + } -// int temp = frontTex; -// frontTex = backTex; -// backTex = temp; + // Swap textures. + int temp = frontTex; + frontTex = backTex; + backTex = temp; - - // This is the trick to avoid tre flickering: don't leave the FBO bound! + // This is the trick to avoid the flickering: don't leave the FBO bound! GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); - PGraphicsOpenGL.screenFramebuffer.glFbo = 0; } -*/ + + /* if (!clear0 || FORCE_SCREEN_FBO) { // We are in the primary surface, and no clear mode, this means that the // current contents of the front buffer needs to be used in the next frame // as the background. GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); - PGraphicsOpenGL.screenFramebuffer.glFbo = 0; + PGraphicsOpenGL.drawFramebuffer.glFbo = 0; GLES20.glClearDepthf(1); GLES20.glClearColor(0, 0, 0, 0); @@ -730,14 +780,7 @@ public class PGL { frontTex = backTex; backTex = temp; } - } - - - protected void beginOffscreenDraw(boolean clear) { - } - - - protected void endOffscreenDraw(boolean clear0) { + */ } diff --git a/android/core/src/processing/opengl/PGraphics2D.java b/android/core/src/processing/opengl/PGraphics2D.java index 27aa13172..509a6f909 100644 --- a/android/core/src/processing/opengl/PGraphics2D.java +++ b/android/core/src/processing/opengl/PGraphics2D.java @@ -120,7 +120,7 @@ public class PGraphics2D extends PGraphicsOpenGL { @Override protected void defaultPerspective() { - super.ortho(-width/2, +width/2, -height/2, +height/2, -1, +1); + super.ortho(0, width, 0, height, -1, +1); } diff --git a/android/core/src/processing/opengl/PGraphics3D.java b/android/core/src/processing/opengl/PGraphics3D.java index 3fa04900e..1924206bd 100644 --- a/android/core/src/processing/opengl/PGraphics3D.java +++ b/android/core/src/processing/opengl/PGraphics3D.java @@ -85,7 +85,7 @@ public class PGraphics3D extends PGraphicsOpenGL { @Override protected void begin2D() { pushProjection(); - ortho(-width/2, +width/2, -height/2, +height/2, -1, +1); + ortho(0, width, 0, height, -1, +1); pushMatrix(); camera(width/2, height/2); } diff --git a/android/core/src/processing/opengl/PGraphicsOpenGL.java b/android/core/src/processing/opengl/PGraphicsOpenGL.java index 7946d232d..dab31340c 100644 --- a/android/core/src/processing/opengl/PGraphicsOpenGL.java +++ b/android/core/src/processing/opengl/PGraphicsOpenGL.java @@ -49,6 +49,57 @@ public class PGraphicsOpenGL extends PGraphics { // ........................................................ + static final String BLEND_DRIVER_ERROR = + "blendMode(%1$s) is not supported by this hardware (or driver)"; + static final String BLEND_RENDERER_ERROR = + "blendMode(%1$s) is not supported by this renderer"; + static final String ALREADY_DRAWING_ERROR = + "Already called beginDraw()"; + static final String NO_BEGIN_DRAW_ERROR = + "Cannot call endDraw() before beginDraw()"; + static final String NESTED_DRAW_ERROR = + "Already called drawing on another PGraphicsOpenGL object"; + static final String ALREADY_BEGAN_CONTOUR_ERROR = + "Already called beginContour()"; + static final String NO_BEGIN_CONTOUR_ERROR = + "Need to call beginContour() first"; + static final String UNSUPPORTED_SMOOTH_LEVEL_ERROR = + "Smooth level %1$s is not available. Using %2$s instead"; + static final String UNSUPPORTED_SMOOTH_ERROR = + "Smooth is not supported by this hardware (or driver)"; + static final String TOO_MANY_SMOOTH_CALLS_ERROR = + "The smooth/noSmooth functions are being called too often.\n" + + "This results in screen flickering, so they will be disabled\n" + + "for the rest of the sketch's execution"; + static final String UNSUPPORTED_SHAPE_FORMAT_ERROR = + "Unsupported shape format"; + static final String INVALID_FILTER_SHADER_ERROR = + "Object is not a valid shader to use as filter"; + static final String INVALID_PROCESSING_SHADER_ERROR = + "The GLSL code doesn't seem to contain a valid shader to use in Processing"; + static final String WRONG_SHADER_TYPE_ERROR = + "shader() called with a wrong shader"; + static final String UNKNOWN_SHADER_KIND_ERROR = + "Unknown shader kind"; + static final String NO_TEXLIGHT_SHADER_ERROR = + "Your shader cannot be used to render textured " + + "and lit geometry, using default shader instead."; + static final String NO_LIGHT_SHADER_ERROR = + "Your shader cannot be used to render lit " + + "geometry, using default shader instead."; + static final String NO_TEXTURE_SHADER_ERROR = + "Your shader cannot be used to render textured " + + "geometry, using default shader instead."; + static final String NO_COLOR_SHADER_ERROR = + "Your shader cannot be used to render colored " + + "geometry, using default shader instead."; + static final String TOO_LONG_STROKE_PATH_ERROR = + "Stroke path is too long, some bevel triangles won't be added"; + static final String TESSELLATION_ERROR = + "Tessellation Error: %1$s"; + + // ........................................................ + // Basic rendering parameters: /** Flush modes: continuously (geometry is flushed after each call to @@ -365,7 +416,8 @@ public class PGraphicsOpenGL extends PGraphics { static protected int fbStackDepth; static protected FrameBuffer[] fbStack = new FrameBuffer[FB_STACK_DEPTH]; - static protected FrameBuffer screenFramebuffer; + static protected FrameBuffer drawFramebuffer; + static protected FrameBuffer readFramebuffer; static protected FrameBuffer currentFramebuffer; // ....................................................... @@ -373,23 +425,20 @@ public class PGraphicsOpenGL extends PGraphics { // Offscreen rendering: protected FrameBuffer offscreenFramebuffer; - protected FrameBuffer offscreenFramebufferMultisample; + protected FrameBuffer multisampleFramebuffer; protected boolean offscreenMultisample; - protected boolean offscreenNotCurrent; + protected boolean pixOpChangedFB; // ........................................................ // Screen surface: - /** A handy reference to the PTexture bound to the drawing surface - * (off or on-screen) */ + /** Texture containing the current frame */ protected Texture texture; - /** Used to create a temporary copy of the color buffer of this - * rendering surface when applying a filter */ -// protected Texture textureCopy; -// protected PImage imageCopy; + /** Texture containing the previous frame */ + protected Texture ptexture; /** IntBuffer wrapping the pixels array. */ protected IntBuffer pixelBuffer; @@ -420,14 +469,11 @@ public class PGraphicsOpenGL extends PGraphics { protected int lastSmoothCall = -10; /** Type of pixels operation. */ - static protected final int OP_NONE = 0; - static protected final int OP_READ = 1; + static protected final int OP_NONE = 0; + static protected final int OP_READ = 1; static protected final int OP_WRITE = 2; protected int pixelsOp = OP_NONE; - /** Used to detect the occurrence of a frame resize event. */ - protected boolean resized = false; - /** Viewport dimensions. */ protected int[] viewport = {0, 0, 0, 0}; @@ -472,7 +518,6 @@ public class PGraphicsOpenGL extends PGraphics { public PGraphicsOpenGL() { pgl = new PGL(this); - if (tessellator == null) { tessellator = new Tessellator(); } @@ -531,17 +576,9 @@ public class PGraphicsOpenGL extends PGraphics { @Override public void setSize(int iwidth, int iheight) { - resized = (0 < width && width != iwidth) || - (0 < height && height != iwidth); - width = iwidth; height = iheight; - if (pixels != null) { - // The user is using the pixels array, so we need to resize accordingly - allocatePixels(); - } - allocate(); reapplySettings(); @@ -554,11 +591,12 @@ public class PGraphicsOpenGL extends PGraphics { cameraFar = cameraZ * 10.0f; cameraAspect = (float) width / (float) height; + // Forces a restart of OpenGL so the canvas has the right size. + //pgl.initialized = false; + restartPGL(); + // set this flag so that beginDraw() will do an update to the camera. sized = true; - - // Forces a restart of OpenGL so the canvas has the right size. - pgl.initialized = false; } @@ -672,7 +710,7 @@ public class PGraphicsOpenGL extends PGraphics { GLResource res = new GLResource(id, context); if (glTextureObjects.containsKey(res)) { - showWarning("Adding same texture twice"); + throw new RuntimeException("Adding same texture twice"); } else { glTextureObjects.put(res, false); } @@ -740,7 +778,7 @@ public class PGraphicsOpenGL extends PGraphics { GLResource res = new GLResource(id, context); if (glVertexBuffers.containsKey(res)) { - showWarning("Adding same VBO twice"); + throw new RuntimeException("Adding same VBO twice"); } else { glVertexBuffers.put(res, false); } @@ -808,7 +846,7 @@ public class PGraphicsOpenGL extends PGraphics { GLResource res = new GLResource(id, context); if (glFrameBuffers.containsKey(res)) { - showWarning("Adding same FBO twice"); + throw new RuntimeException("Adding same FBO twice"); } else { glFrameBuffers.put(res, false); } @@ -876,7 +914,7 @@ public class PGraphicsOpenGL extends PGraphics { GLResource res = new GLResource(id, context); if (glRenderBuffers.containsKey(res)) { - showWarning("Adding same renderbuffer twice"); + throw new RuntimeException("Adding same renderbuffer twice"); } else { glRenderBuffers.put(res, false); } @@ -942,7 +980,7 @@ public class PGraphicsOpenGL extends PGraphics { GLResource res = new GLResource(id, context); if (glslPrograms.containsKey(res)) { - showWarning("Adding same glsl program twice"); + throw new RuntimeException("Adding same glsl program twice"); } else { glslPrograms.put(res, false); } @@ -1005,7 +1043,7 @@ public class PGraphicsOpenGL extends PGraphics { GLResource res = new GLResource(id, context); if (glslVertexShaders.containsKey(res)) { - showWarning("Adding same glsl vertex shader twice"); + throw new RuntimeException("Adding same glsl vertex shader twice"); } else { glslVertexShaders.put(res, false); } @@ -1069,7 +1107,7 @@ public class PGraphicsOpenGL extends PGraphics { GLResource res = new GLResource(id, context); if (glslFragmentShaders.containsKey(res)) { - showWarning("Adding same glsl fragment shader twice"); + throw new RuntimeException("Adding same glsl fragment shader twice"); } else { glslFragmentShaders.put(res, false); } @@ -1535,18 +1573,18 @@ public class PGraphicsOpenGL extends PGraphics { @Override public void beginDraw() { + report("top beginDraw()"); + if (drawing) { - showWarning("Already called beginDraw()."); + PGraphics.showWarning(ALREADY_DRAWING_ERROR); return; } if (pgCurrent != null && !pgCurrent.primarySurface && !this.primarySurface) { // It seems that the user is trying to start another beginDraw()/endDraw() - // block for an offscreen surface, still drawing on another offscreen - // surface. - showWarning("Already called beginDraw() for another " + - "PGraphicsOpenGL object."); + // block for an offscreen surface, still drawing on another one. + PGraphics.showWarning(NESTED_DRAW_ERROR); return; } @@ -1554,185 +1592,17 @@ public class PGraphicsOpenGL extends PGraphics { getGLParameters(); } - if (screenFramebuffer == null) { - screenFramebuffer = new FrameBuffer(parent, width, height, true); - setFramebuffer(screenFramebuffer); - } - if (primarySurface) { - pgl.updatePrimary(); - pgl.drawBuffer(pgl.primaryDrawBuffer()); + updatePrimary(); + pgl.beginDraw(clearColorBuffer); } else { - if (!pgl.initialized) { - initOffscreen(); - } else { - boolean outdated = offscreenFramebuffer != null && - offscreenFramebuffer.contextIsOutdated(); - boolean outdatedMulti = offscreenFramebufferMultisample != null && - offscreenFramebufferMultisample.contextIsOutdated(); - if (outdated || outdatedMulti) { - pgl.initialized = false; - initOffscreen(); - } - } - - pushFramebuffer(); - if (offscreenMultisample) { - setFramebuffer(offscreenFramebufferMultisample); - } else { - setFramebuffer(offscreenFramebuffer); - } - pgl.updateOffscreen(pgPrimary.pgl); - pgl.drawBuffer(PGL.COLOR_ATTACHMENT0); + updateOffscreen(); + beginOffscreenDraw(); } - // We are ready to go! - report("top beginDraw()"); - - drawing = true; + setDefaults(); pgCurrent = this; - - inGeo.clear(); - tessGeo.clear(); - texCache.clear(); - - // Each frame starts with textures disabled. - super.noTexture(); - - // Screen blend is needed for alpha (i.e. fonts) to work. - // Using setDefaultBlend() instead of blendMode() because - // the latter will set the blend mode only if it is different - // from current. - setDefaultBlend(); - - // this is necessary for 3D drawing - if (hints[DISABLE_DEPTH_TEST]) { - pgl.disable(PGL.DEPTH_TEST); - } else { - pgl.enable(PGL.DEPTH_TEST); - } - // use <= since that's what processing.core does - pgl.depthFunc(PGL.LEQUAL); - - if (hints[ENABLE_ACCURATE_2D]) { - flushMode = FLUSH_CONTINUOUSLY; - } else { - flushMode = FLUSH_WHEN_FULL; - } - - if (primarySurface) { - int[] temp = new int[1]; - pgl.getIntegerv(PGL.SAMPLES, temp, 0); - if (quality != temp[0] && 1 < temp[0] && 1 < quality) { - quality = temp[0]; - } - } - if (quality < 2) { - pgl.disable(PGL.MULTISAMPLE); - } else { - pgl.enable(PGL.MULTISAMPLE); - } - pgl.disable(PGL.POINT_SMOOTH); - pgl.disable(PGL.LINE_SMOOTH); - pgl.disable(PGL.POLYGON_SMOOTH); - - // setup opengl viewport. - viewport[0] = 0; viewport[1] = 0; viewport[2] = width; viewport[3] = height; - pgl.viewport(viewport[0], viewport[1], viewport[2], viewport[3]); - if (resized) { - // To avoid having garbage in the screen after a resize, - // in the case background is not called in draw(). - background(backgroundColor); - if (texture != null) { - // The screen texture should be deleted because it - // corresponds to the old window size. - pgPrimary.removeCache(this); - texture = null; - loadTexture(); - } - resized = false; - } - - if (sized) { - // Sets the default projection and camera (initializes modelview). - // If the user has setup up their own projection, they'll need - // to fix it after resize anyway. This helps the people who haven't - // set up their own projection. - defaultPerspective(); - defaultCamera(); - - // clear the flag - sized = false; - } else { - // Eliminating any user's transformations by going back to the - // original camera setup. - modelview.set(camera); - modelviewInv.set(cameraInv); - calcProjmodelview(); - } - - if (is3D()) { - noLights(); - lightFalloff(1, 0, 0); - lightSpecular(0, 0, 0); - } - - // Because y is flipped, the vertices that should be specified by - // the user in CCW order to define a front-facing facet, end up being CW. - pgl.frontFace(PGL.CW); - pgl.disable(PGL.CULL_FACE); - - // Processing uses only one texture unit. - pgl.activeTexture(PGL.TEXTURE0); - - // The current normal vector is set to be parallel to the Z axis. - normalX = normalY = normalZ = 0; - - // Clear depth and stencil buffers. - pgl.depthMask(true); - pgl.clearDepth(1); - pgl.clearStencil(0); - pgl.clear(PGL.DEPTH_BUFFER_BIT | PGL.STENCIL_BUFFER_BIT); - - if (primarySurface) { - pgl.beginOnscreenDraw(clearColorBuffer); - } else { - pgl.beginOffscreenDraw(pgPrimary.clearColorBuffer); - - // Just in case the texture was recreated (in a resize event for example) - offscreenFramebuffer.setColorBuffer(texture); - - // Restoring the clipping configuration of the offscreen surface. - if (clip) { - pgl.enable(PGL.SCISSOR_TEST); - pgl.scissor(clipRect[0], clipRect[1], clipRect[2], clipRect[3]); - } else { - pgl.disable(PGL.SCISSOR_TEST); - } - } - - if (!settingsInited) { - defaultSettings(); - } - - if (restoreSurface) { - restoreSurfaceFromPixels(); - restoreSurface = false; - } - - if (hints[DISABLE_DEPTH_MASK]) { - pgl.depthMask(false); - } else { - pgl.depthMask(true); - } - - pixelsOp = OP_NONE; - - modified = false; - setgetPixels = false; - - clearColorBuffer0 = clearColorBuffer; - clearColorBuffer = false; + drawing = true; report("bot beginDraw()"); } @@ -1742,69 +1612,39 @@ public class PGraphicsOpenGL extends PGraphics { public void endDraw() { report("top endDraw()"); - // Flushing any remaining geometry. - flush(); - if (!drawing) { - showWarning("Cannot call endDraw() before beginDraw()."); + PGraphics.showWarning(NO_BEGIN_DRAW_ERROR); return; } - if (primarySurface) { - pgl.endOnscreenDraw(clearColorBuffer0); + // Flushing any remaining geometry. + flush(); - if (!pgl.initialized || parent.frameCount == 0) { - // TODO: check this code and see if should go before endOnscreenDraw - // Smooth was called at some point during drawing. We save - // the current contents of the back buffer (because the - // buffers haven't been swapped yet) to the pixels array. - // The frameCount == 0 condition is to handle the situation when - // no smooth is called in setup in the PDE, but the OpenGL appears to - // be recreated due to the size() nastiness. - saveSurfaceToPixels(); - restoreSurface = true; - } - - pgl.flush(); - } else { - if (offscreenMultisample) { - offscreenFramebufferMultisample.copy(offscreenFramebuffer); - } - - if (!pgl.initialized || !pgPrimary.pgl.initialized || - parent.frameCount == 0) { - // If the primary surface is re-initialized, this offscreen - // surface needs to save its contents into the pixels array - // so they can be restored after the FBOs are recreated. - // Note that a consequence of how this is code works, is that - // if the user changes the smooth level of the primary surface - // in the middle of draw, but after drawing the offscreen surfaces - // then these won't be restored in the next frame since their - // endDraw() calls didn't pick up any change in the initialization - // state of the primary surface. - saveSurfaceToPixels(); - restoreSurface = true; - } - - popFramebuffer(); - - texture.updateTexels(); // Mark all texels in screen texture as modified. - - pgl.endOffscreenDraw(pgPrimary.clearColorBuffer0); - - pgPrimary.restoreGL(); + if (!pgPrimary.pgl.initialized || parent.frameCount == 0) { + // Smooth was disabled/enabled at some point during drawing. We save + // the current contents of the back buffer (because the buffers haven't + // been swapped yet) to the pixels array. The frameCount == 0 condition + // is to handle the situation when no smooth is called in setup in the + // PDE, but the OpenGL appears to be recreated due to the size() nastiness. + saveSurfaceToPixels(); + restoreSurface = true; + } + + if (primarySurface) { + pgl.endDraw(clearColorBuffer0); + pgl.flush(); + } else { + endOffscreenDraw(); } - // Done! - drawing = false; if (pgCurrent == pgPrimary) { // Done with the main surface pgCurrent = null; } else { - // Done with an offscreen surface, - // going back to onscreen drawing. + // Done with an offscreen surface, going back to onscreen drawing. pgCurrent = pgPrimary; } + drawing = false; report("bot endDraw()"); } @@ -1866,87 +1706,74 @@ public class PGraphicsOpenGL extends PGraphics { pgl.depthMask(true); } - pgl.drawBuffer(pgl.primaryDrawBuffer()); + currentFramebuffer.bind(); + pgl.drawBuffer(currentFramebuffer.getDefaultDrawBuffer()); } protected void beginPixelsOp(int op) { + FrameBuffer pixfb = null; if (primarySurface) { - if (pgl.primaryIsFboBacked()) { - if (op == OP_READ) { - // We read from the color FBO, but the multisample FBO is currently - // bound, so: - offscreenNotCurrent = true; - pgl.bindPrimaryColorFBO(); - pgl.readBuffer(pgl.primaryDrawBuffer()); + if (op == OP_READ) { + if (pgl.isFBOBacked() && pgl.isMultisampled()) { + // Making sure the back texture is up-to-date... + pgl.syncBackTexture(); + // ...because the read framebuffer uses it as the color buffer (the + // draw framebuffer is MSAA so it cannot be read from it). + pixfb = readFramebuffer; } else { - // We write directly to the multisample FBO. - offscreenNotCurrent = false; - pgl.drawBuffer(pgl.primaryDrawBuffer()); + pixfb = drawFramebuffer; } - } else { - // We read or write from the back buffer, where all the - // drawing in the current frame is taking place. - if (op == OP_READ) { - pgl.readBuffer(pgl.primaryDrawBuffer()); - } else { - pgl.drawBuffer(pgl.primaryDrawBuffer()); - } - offscreenNotCurrent = false; + } else if (op == OP_WRITE) { + // We can write to the draw framebuffer irrespective of whether is + // FBO-baked or multisampled. + pixfb = drawFramebuffer; } } else { - // Making sure that the offscreen FBO is current. This allows to do calls - // like loadPixels(), set() or get() without enclosing them between - // beginDraw()/endDraw() when working with a PGraphics object. We don't - // need the rest of the surface initialization/finalization, since only - // the pixels are affected. if (op == OP_READ) { - // We always read the screen pixels from the color FBO. - offscreenNotCurrent = offscreenFramebuffer != currentFramebuffer; - if (offscreenNotCurrent) { - pushFramebuffer(); - setFramebuffer(offscreenFramebuffer); - pgl.updateOffscreen(pgPrimary.pgl); + if (offscreenMultisample) { + // Making sure the offscreen FBO is up-to-date + multisampleFramebuffer.copy(offscreenFramebuffer, currentFramebuffer); } - pgl.readBuffer(PGL.COLOR_ATTACHMENT0); - } else { + // We always read the screen pixels from the color FBO. + pixfb = offscreenFramebuffer; + } else if (op == OP_WRITE) { // We can write directly to the color FBO, or to the multisample FBO // if multisampling is enabled. - if (offscreenMultisample) { - offscreenNotCurrent = offscreenFramebufferMultisample != - currentFramebuffer; - } else { - offscreenNotCurrent = offscreenFramebuffer != currentFramebuffer; - } - if (offscreenNotCurrent) { - pushFramebuffer(); - if (offscreenMultisample) { - setFramebuffer(offscreenFramebufferMultisample); - } else { - setFramebuffer(offscreenFramebuffer); - } - pgl.updateOffscreen(pgPrimary.pgl); - } - pgl.drawBuffer(PGL.COLOR_ATTACHMENT0); + pixfb = offscreenMultisample ? multisampleFramebuffer : + offscreenFramebuffer; } } + + // Set the framebuffer where the pixel operation shall be carried out. + if (pixfb != currentFramebuffer) { + pushFramebuffer(); + setFramebuffer(pixfb); + pixOpChangedFB = true; + } + + // We read from/write to the draw buffer. + if (op == OP_READ) { + pgl.readBuffer(currentFramebuffer.getDefaultDrawBuffer()); + } else if (op == OP_WRITE) { + pgl.drawBuffer(currentFramebuffer.getDefaultDrawBuffer()); + } + pixelsOp = op; } protected void endPixelsOp() { - if (offscreenNotCurrent) { - if (primarySurface) { - pgl.bindPrimaryMultiFBO(); - } else { - if (pixelsOp == OP_WRITE && offscreenMultisample) { - // We were writing to the multisample FBO, so we need - // to blit its contents to the color FBO. - offscreenFramebufferMultisample.copy(offscreenFramebuffer); - } - popFramebuffer(); - } + // Restoring current framebuffer prior to the pixel operation + if (pixOpChangedFB) { + popFramebuffer(); + pixOpChangedFB = false; } + + // Restoring default read/draw buffer configuration. + pgl.readBuffer(currentFramebuffer.getDefaultReadBuffer()); + pgl.drawBuffer(currentFramebuffer.getDefaultDrawBuffer()); + pixelsOp = OP_NONE; } @@ -2156,7 +1983,7 @@ public class PGraphicsOpenGL extends PGraphics { @Override public void beginShape(int kind) { shape = kind; - + curveVertexCount = 0; inGeo.clear(); breakShape = true; @@ -2215,7 +2042,7 @@ public class PGraphicsOpenGL extends PGraphics { @Override public void beginContour() { if (openContour) { - showWarning("Already called beginContour()."); + PGraphics.showWarning(ALREADY_BEGAN_CONTOUR_ERROR); return; } openContour = true; @@ -2226,7 +2053,7 @@ public class PGraphicsOpenGL extends PGraphics { @Override public void endContour() { if (!openContour) { - showWarning("Need to call beginContour() first."); + PGraphics.showWarning(NO_BEGIN_CONTOUR_ERROR); return; } openContour = false; @@ -2305,39 +2132,6 @@ public class PGraphicsOpenGL extends PGraphics { @Override - public void clip(float a, float b, float c, float d) { - if (imageMode == CORNER) { - if (c < 0) { // reset a negative width - a += c; c = -c; - } - if (d < 0) { // reset a negative height - b += d; d = -d; - } - - clipImpl(a, b, a + c, b + d); - - } else if (imageMode == CORNERS) { - if (c < a) { // reverse because x2 < x1 - float temp = a; a = c; c = temp; - } - if (d < b) { // reverse because y2 < y1 - float temp = b; b = d; d = temp; - } - - clipImpl(a, b, c, d); - - } else if (imageMode == CENTER) { - // c and d are width/height - if (c < 0) c = -c; - if (d < 0) d = -d; - float x1 = a - c/2; - float y1 = b - d/2; - - clipImpl(x1, y1, x1 + c, y1 + d); - } - } - - protected void clipImpl(float x1, float y1, float x2, float y2) { flush(); pgl.enable(PGL.SCISSOR_TEST); @@ -2533,7 +2327,7 @@ public class PGraphicsOpenGL extends PGraphics { // If the renderer is 2D, then lights should always be false, // so no need to worry about that. - PolyShader shader = getPolyShader(lights, tex != null); + BaseShader shader = getPolyShader(lights, tex != null); shader.bind(); int first = texCache.firstCache[i]; @@ -3259,11 +3053,9 @@ public class PGraphicsOpenGL extends PGraphics { if (maxSamples < level) { if (0 < maxSamples) { - PGraphics.showWarning("Smooth level " + level + - " is not available. Using " + - maxSamples + " instead."); + PGraphics.showWarning(UNSUPPORTED_SMOOTH_LEVEL_ERROR, level, maxSamples); } else{ - PGraphics.showWarning("Smooth is not available."); + PGraphics.showWarning(UNSUPPORTED_SMOOTH_ERROR); } level = maxSamples; } @@ -3272,11 +3064,7 @@ public class PGraphicsOpenGL extends PGraphics { smoothCallCount++; if (parent.frameCount - lastSmoothCall < 30 && 5 < smoothCallCount) { smoothDisabled = true; - PGraphics.showWarning("The smooth/noSmooth functions are being " + - "called too often.\n" + - "This results in screen flickering, so they " + - "will be disabled\n" + - "for the rest of the sketch's execution."); + PGraphics.showWarning(TOO_MANY_SMOOTH_CALLS_ERROR); } lastSmoothCall = parent.frameCount; @@ -3284,9 +3072,10 @@ public class PGraphicsOpenGL extends PGraphics { if (quality == 1) { quality = 0; } + // This will trigger a surface restart next time // requestDraw() is called. - pgl.initialized = false; + restartPGL(); } } @@ -3301,18 +3090,15 @@ public class PGraphicsOpenGL extends PGraphics { smoothCallCount++; if (parent.frameCount - lastSmoothCall < 30 && 5 < smoothCallCount) { smoothDisabled = true; - PGraphics.showWarning("The smooth/noSmooth functions are being " + - "called too often.\n" + - "This results in screen flickering, so they " + - "will be disabled\n" + - "for the rest of the sketch's execution."); + PGraphics.showWarning(TOO_MANY_SMOOTH_CALLS_ERROR); } lastSmoothCall = parent.frameCount; quality = 0; + // This will trigger a surface restart next time // requestDraw() is called. - pgl.initialized = false; + restartPGL(); } } @@ -3399,7 +3185,7 @@ public class PGraphicsOpenGL extends PGraphics { } if (PGraphics3D.isSupportedExtension(ext)) { return PGraphics3D.loadShapeImpl(this, filename, ext); } else { - PGraphics.showWarning("Unsupported format"); + PGraphics.showWarning(UNSUPPORTED_SHAPE_FORMAT_ERROR); return null; } } @@ -4268,7 +4054,7 @@ public class PGraphicsOpenGL extends PGraphics { */ @Override public void ortho() { - ortho(-width/2, +width/2, -height/2, +height/2, cameraNear, cameraFar); + ortho(0, width, 0, height, cameraNear, cameraFar); } @@ -4284,33 +4070,18 @@ public class PGraphicsOpenGL extends PGraphics { /** - * Sets an orthographic projection. The left, right, bottom and top - * values refer to the center of the screen, with the y-axis inverted - * with respect to the default orientation in Processing, e.g: botton-to-top - * instead of top-to-bottom. - * In particular, if we need to set the the orthographic projection exactly - * covering the rectangle starting at the origin and having the same - * dimensions as the screen size, then we would pass: - * left = -width/2 - * right = +width/2 - * bottom = -height/2 - * top = +height/2 - * In general, if we want to set an ortographic projection covering the - * rectangle determined by the top-left corner (x0, y0) and bottom-right - * corner (x1, y1), we would need to pass the following parameters: - * left = x0 - width/2 - * right = x1 - width/2 - * bottom = height/2 - y1 - * top = height/2 - y0 - * that just correspond to the change of coordinates between the - * coordinate system located at the screen center with the Y-axis - * bottom-to-top, and Processing's system. + * Sets an orthographic projection. * */ @Override public void ortho(float left, float right, float bottom, float top, float near, float far) { + left -= width/2; + right -= width/2; + bottom -= height/2; + top -= height/2; + // Flushing geometry with a different perspective configuration. flush(); @@ -5187,6 +4958,12 @@ public class PGraphicsOpenGL extends PGraphics { // color buffer into it. @Override public void loadPixels() { + if (sized) { + // Something wrong going on with threading, sized can never be true if the + // all the steps in a resize happen inside the Animation thread. + return; + } + boolean needEndDraw = false; if (!drawing) { beginDraw(); @@ -5211,6 +4988,14 @@ public class PGraphicsOpenGL extends PGraphics { } + protected void allocatePixels() { + if ((pixels == null) || (pixels.length != width * height)) { + pixels = new int[width * height]; + pixelBuffer = IntBuffer.wrap(pixels); + } + } + + protected void saveSurfaceToPixels() { allocatePixels(); readPixels(); @@ -5222,54 +5007,88 @@ public class PGraphicsOpenGL extends PGraphics { } - protected void allocatePixels() { - if ((pixels == null) || (pixels.length != width * height)) { - pixels = new int[width * height]; - pixelBuffer = IntBuffer.wrap(pixels); + protected void readPixels() { + beginPixelsOp(OP_READ); + pixelBuffer.rewind(); + try { + // The readPixels() call in inside a try/catch block because it appears + // that (only sometimes) JOGL will run beginDraw/endDraw on the EDT + // thread instead of the Animation thread right after a resize. Because + // of this the width and height might have a different size than the + // one of the pixels arrays. + pgl.readPixels(0, 0, width, height, PGL.RGBA, PGL.UNSIGNED_BYTE, + pixelBuffer); + } catch (IndexOutOfBoundsException e) { + // Silently catch the exception. + } + endPixelsOp(); + try { + // Idem... + PGL.nativeToJavaARGB(pixels, width, height); + } catch (ArrayIndexOutOfBoundsException e) { } } - protected void readPixels() { - beginPixelsOp(OP_READ); - pixelBuffer.rewind(); - pgl.readPixels(0, 0, width, height, PGL.RGBA, PGL.UNSIGNED_BYTE, - pixelBuffer); - endPixelsOp(); - - PGL.nativeToJavaARGB(pixels, width, height); - } - - protected void drawPixels(int x, int y, int w, int h) { - int i0 = y * width + x; int len = w * h; - if (nativePixels == null || nativePixels.length < len) { nativePixels = new int[len]; nativePixelBuffer = IntBuffer.wrap(nativePixels); } - PApplet.arrayCopy(pixels, i0, nativePixels, 0, len); - PGL.javaToNativeARGB(nativePixels, w, h); + try { + if (0 < x || 0 < y || w < width || h < height) { + // The pixels to copy to the texture need to be consecutive, and they + // are not in the pixels array, so putting each row one after another + // in nativePixels. + int offset0 = y * width + x; + int offset1 = 0; + + for (int yc = y; yc < y + h; yc++) { + System.arraycopy(pixels, offset0, nativePixels, offset1, w); + offset0 += width; + offset1 += w; + } + } else { + PApplet.arrayCopy(pixels, 0, nativePixels, 0, len); + } + PGL.javaToNativeARGB(nativePixels, w, h); + } catch (ArrayIndexOutOfBoundsException e) { + } // Copying pixel buffer to screen texture... - if (primarySurface) { - loadTextureImpl(POINT, false); // (first making sure that the screen texture is valid). + if (primarySurface && !pgl.isFBOBacked()) { + // First making sure that the screen texture is valid. Only in the case + // of non-FBO-backed primary surface we might need to create the texture. + loadTextureImpl(POINT, false); } - pgl.copyToTexture(texture.glTarget, texture.glFormat, texture.glName, - x, y, w, h, IntBuffer.wrap(nativePixels)); - if (primarySurface || offscreenMultisample) { - // ...and drawing the texture to screen... but only - // if we are on the primary surface or we have - // multisampled FBO. Why? Because in the case of non- - // multisampled FBO, texture is actually the color buffer - // used by the color FBO, so with the copy operation we - // should be done updating the (off)screen buffer. + boolean needToDrawTex = primarySurface && (!pgl.isFBOBacked() || + (pgl.isFBOBacked() && pgl.isMultisampled())) || + offscreenMultisample; + if (needToDrawTex) { + // The texture to screen needs to be drawn only if we are on the primary + // surface w/out FBO-layer, or with FBO-layer and multisampling. Or, we + // are doing multisampled offscreen. Why? Because in the case of + // non-multisampled FBO, texture is actually the color buffer used by the + // color FBO, so with the copy operation we should be done updating the + // (off)screen buffer. + + // First, copy the pixels to the texture. We don't need to invert the + // pixel copy because the texture will be drawn inverted. + pgl.copyToTexture(texture.glTarget, texture.glFormat, texture.glName, + x, y, w, h, IntBuffer.wrap(nativePixels)); + beginPixelsOp(OP_WRITE); drawTexture(x, y, w, h); endPixelsOp(); + } else { + // We only need to copy the pixels to the back texture where we are + // currently drawing to. Because the texture is invertex along Y, we + // need to reflect that in the vertical arguments. + pgl.copyToTexture(texture.glTarget, texture.glFormat, texture.glName, + x, height - (y + h), w, h, IntBuffer.wrap(nativePixels)); } } @@ -5307,11 +5126,14 @@ public class PGraphicsOpenGL extends PGraphics { @Override - protected void setImpl(PImage src, - int sx, int sy, int sw, int sh, int dx, int dy) { + protected void setImpl(PImage sourceImage, + int sourceX, int sourceY, + int sourceWidth, int sourceHeight, + int targetX, int targetY) { loadPixels(); setgetPixels = true; - super.setImpl(src, sx, sy, sw, sh, dx, dy); + super.setImpl(sourceImage, sourceX, sourceY, sourceWidth, sourceHeight, + targetX, targetY); } @@ -5332,17 +5154,13 @@ public class PGraphicsOpenGL extends PGraphics { flush(); // To make sure the color buffer is updated. if (primarySurface) { - loadTextureImpl(Texture.POINT, false); - - if (pgl.primaryIsFboBacked()) { - pgl.bindPrimaryColorFBO(); - // Copy the contents of the FBO used by the primary surface into - // texture, this copy operation is very fast because it is resolved - // in the GPU. - texture.set(pgl.getFboTexTarget(), pgl.getFboTexName(), - pgl.getFboWidth(), pgl.getFboHeight(), width, height); - pgl.bindPrimaryMultiFBO(); + if (pgl.isFBOBacked()) { + // In the case of MSAA, this is needed so the back buffer is in sync + // with the rendering. + pgl.syncBackTexture(); } else { + loadTextureImpl(Texture.POINT, false); + // Here we go the slow route: we first copy the contents of the color // buffer into a pixels array (but we keep it in native format) and // then copy this array into the texture. @@ -5352,8 +5170,12 @@ public class PGraphicsOpenGL extends PGraphics { } beginPixelsOp(OP_READ); - pgl.readPixels(0, 0, width, height, PGL.RGBA, PGL.UNSIGNED_BYTE, - nativePixelBuffer); + try { + // Se comments in readPixels() for the reason for this try/catch. + pgl.readPixels(0, 0, width, height, PGL.RGBA, PGL.UNSIGNED_BYTE, + nativePixelBuffer); + } catch (IndexOutOfBoundsException e) { + } endPixelsOp(); texture.setNative(nativePixels, 0, 0, width, height); @@ -5362,7 +5184,7 @@ public class PGraphicsOpenGL extends PGraphics { // We need to copy the contents of the multisampled buffer to the // color buffer, so the later is up-to-date with the last drawing. if (offscreenMultisample) { - offscreenFramebufferMultisample.copy(offscreenFramebuffer); + multisampleFramebuffer.copy(offscreenFramebuffer, currentFramebuffer); } } @@ -5394,40 +5216,6 @@ public class PGraphicsOpenGL extends PGraphics { } - // Uses the texture in img as the color buffer for this surface. - public void setTexture(PImage img) { - if (width != img.width || height != img.height) { - PGraphics.showWarning("Resolution of image is different from PGraphics " + - "object"); - return; - } - - if (texture == null || texture != pgPrimary.getCache(img)) { - Texture tex = (Texture)pgPrimary.getCache(img); - Texture.Parameters params = tex != null ? tex.getParameters() : null; - if (tex == null || tex.contextIsOutdated() || !validSurfaceTex(tex)) { - if (primarySurface) { - params = new Texture.Parameters(ARGB, Texture.POINT, false); - } else { - params = new Texture.Parameters(ARGB, Texture.BILINEAR, false); - } - tex = addTexture(img, params); - } - if (tex != null) { - texture = tex; - texture.invertedY(true); - pgPrimary.setCache(this, texture); - - if (!primarySurface && offscreenFramebuffer != null) { - // Attach as the color buffer for this offscreen surface - offscreenFramebuffer.setColorBuffer(texture); - offscreenFramebuffer.clear(); - } - } - } - } - - public void drawTexture(int target, int id, int width, int height, int X0, int Y0, int X1, int Y1) { beginPGL(); @@ -5448,6 +5236,7 @@ public class PGraphicsOpenGL extends PGraphics { protected void loadTextureImpl(int sampling, boolean mipmap) { + if (width == 0 || height == 0) return; if (texture == null || texture.contextIsOutdated()) { Texture.Parameters params = new Texture.Parameters(ARGB, @@ -5456,6 +5245,22 @@ public class PGraphicsOpenGL extends PGraphics { texture.invertedY(true); texture.colorBufferOf(this); pgPrimary.setCache(this, texture); + + if (!primarySurface) { + ptexture = new Texture(parent, width, height, params); + ptexture.invertedY(true); + ptexture.colorBufferOf(this); + } + } + } + + + protected void swapTextures() { + int temp = texture.glName; + texture.glName = ptexture.glName; + ptexture.glName = temp; + if (!primarySurface) { + offscreenFramebuffer.setColorBuffer(texture); } } @@ -5468,70 +5273,26 @@ public class PGraphicsOpenGL extends PGraphics { protected void drawTexture(int x, int y, int w, int h) { + // Processing Y axis is inverted with respect to OpenGL, so we need to + // invert the y coordinates of the screen rectangle. pgl.drawTexture(texture.glTarget, texture.glName, texture.glWidth, texture.glHeight, - x, y, x + w, y + h); + x, y, x + w, y + h, + x, height - (y + h), x + w, height - y); } - protected boolean validSurfaceTex(Texture tex) { - Texture.Parameters params = tex.getParameters(); - if (primarySurface) { - return params.sampling == Texture.POINT && !params.mipmaps; - } else { - return params.sampling == Texture.BILINEAR && !params.mipmaps; - } - } - - - - ////////////////////////////////////////////////////////////// - - // IMAGE CONVERSION - - - /* - static public void nativeToJavaRGB(PImage image) { - if (image.pixels != null) { - PGL.nativeToJavaRGB(image.pixels, image.width, image.height); - } - } - - - static public void nativeToJavaARGB(PImage image) { - if (image.pixels != null) { - PGL.nativeToJavaARGB(image.pixels, image.width, image.height); - } - } - - - static public void javaToNativeRGB(PImage image) { - if (image.pixels != null) { - PGL.javaToNativeRGB(image.pixels, image.width, image.height); - } - } - - - static public void javaToNativeARGB(PImage image) { - if (image.pixels != null) { - PGL.javaToNativeARGB(image.pixels, image.width, image.height); - } - } - */ - - - ////////////////////////////////////////////////////////////// // MASK - @Override - public void mask(int alpha[]) { - PImage temp = get(); - temp.mask(alpha); - set(0, 0, temp); - } +// @Override +// public void mask(int alpha[]) { +// PImage temp = get(); +// temp.mask(alpha); +// set(0, 0, temp); +// } @Override @@ -5585,25 +5346,12 @@ public class PGraphicsOpenGL extends PGraphics { @Override public void filter(PShader shader) { if (!(shader instanceof PolyTexShader)) { - PGraphics.showWarning("Object is not a valid shader"); + PGraphics.showWarning(INVALID_FILTER_SHADER_ERROR); return; } loadTexture(); -/* - if (textureCopy == null || textureCopy.width != width || - textureCopy.height != height) { - Texture.Parameters params = new Texture.Parameters(ARGB, Texture.POINT, - false); - textureCopy = new Texture(parent, width, height, params); - textureCopy.invertedY(true); - imageCopy = wrapTexture(textureCopy); - } - textureCopy.set(texture.glTarget, texture.glName, - texture.glWidth, texture.glHeight, width, height); -*/ - // Disable writing to the depth buffer, so that after applying the filter we // can still use the depth information to keep adding geometry to the scene. pgl.depthMask(false); @@ -5626,7 +5374,6 @@ public class PGraphicsOpenGL extends PGraphics { PolyTexShader prevTexShader = polyTexShader; polyTexShader = (PolyTexShader) shader; beginShape(QUADS); - //texture(imageCopy); texture(this); vertex(0, 0, 0, 0); vertex(width, 0, 1, 0); @@ -5705,12 +5452,14 @@ public class PGraphicsOpenGL extends PGraphics { * Allows to set custom blend modes for the entire scene, using openGL. * Reference article about blending modes: * http://www.pegtop.net/delphi/articles/blendmodes/ + * HARD_LIGHT, SOFT_LIGHT, OVERLAY, DODGE, BURN modes cannot be + * implemented in fixed-function pipeline because they require + * conditional blending and non-linear blending equations. */ @Override public void blendMode(int mode) { if (blendMode != mode) { - // Flushing any remaining geometry that uses a different blending - // mode. + // Flush any geometry that uses a different blending mode. flush(); blendMode = mode; @@ -5721,64 +5470,82 @@ public class PGraphicsOpenGL extends PGraphics { pgl.blendEquation(PGL.FUNC_ADD); } pgl.blendFunc(PGL.ONE, PGL.ZERO); + } else if (mode == BLEND) { if (blendEqSupported) { pgl.blendEquation(PGL.FUNC_ADD); } pgl.blendFunc(PGL.SRC_ALPHA, PGL.ONE_MINUS_SRC_ALPHA); + } else if (mode == ADD) { if (blendEqSupported) { pgl.blendEquation(PGL.FUNC_ADD); } pgl.blendFunc(PGL.SRC_ALPHA, PGL.ONE); + } else if (mode == SUBTRACT) { if (blendEqSupported) { pgl.blendEquation(PGL.FUNC_ADD); } pgl.blendFunc(PGL.ONE_MINUS_DST_COLOR, PGL.ZERO); + } else if (mode == LIGHTEST) { if (blendEqSupported) { pgl.blendEquation(PGL.FUNC_MAX); + pgl.blendFunc(PGL.SRC_ALPHA, PGL.DST_ALPHA); } else { - PGraphics.showWarning("This blend mode is not supported"); - return; + PGraphics.showWarning(BLEND_DRIVER_ERROR, "LIGHTEST"); } - pgl.blendFunc(PGL.SRC_ALPHA, PGL.DST_ALPHA); + } else if (mode == DARKEST) { if (blendEqSupported) { pgl.blendEquation(PGL.FUNC_MIN); + pgl.blendFunc(PGL.SRC_ALPHA, PGL.DST_ALPHA); } else { - PGraphics.showWarning("This blend mode is not supported"); - return; + PGraphics.showWarning(BLEND_DRIVER_ERROR, "DARKEST"); } - pgl.blendFunc(PGL.SRC_ALPHA, PGL.DST_ALPHA); + } else if (mode == DIFFERENCE) { if (blendEqSupported) { pgl.blendEquation(PGL.FUNC_REVERSE_SUBTRACT); + pgl.blendFunc(PGL.ONE, PGL.ONE); } else { - PGraphics.showWarning("This blend mode is not supported"); - return; + PGraphics.showWarning(BLEND_DRIVER_ERROR, "DIFFERENCE"); } - pgl.blendFunc(PGL.ONE, PGL.ONE); + } else if (mode == EXCLUSION) { if (blendEqSupported) { pgl.blendEquation(PGL.FUNC_ADD); } pgl.blendFunc(PGL.ONE_MINUS_DST_COLOR, PGL.ONE_MINUS_SRC_COLOR); + } else if (mode == MULTIPLY) { if (blendEqSupported) { pgl.blendEquation(PGL.FUNC_ADD); } pgl.blendFunc(PGL.DST_COLOR, PGL.SRC_COLOR); + } else if (mode == SCREEN) { if (blendEqSupported) { pgl.blendEquation(PGL.FUNC_ADD); } pgl.blendFunc(PGL.ONE_MINUS_DST_COLOR, PGL.ONE); + + } else if (mode == OVERLAY) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "OVERLAY"); + + } else if (mode == HARD_LIGHT) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "HARD_LIGHT"); + + } else if (mode == SOFT_LIGHT) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "SOFT_LIGHT"); + + } else if (mode == DODGE) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "DODGE"); + + } else if (mode == BURN) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "BURN"); } - // HARD_LIGHT, SOFT_LIGHT, OVERLAY, DODGE, BURN modes cannot be - // implemented in fixed-function pipeline because they require conditional - // blending and non-linear blending equations. } } @@ -5859,18 +5626,18 @@ public class PGraphicsOpenGL extends PGraphics { protected void bindBackTexture() { if (primarySurface) { - pgl.bindBackBufferTex(); + pgl.bindFrontTexture(); } else { - + ptexture.bind(); } } protected void unbindBackTexture() { if (primarySurface) { - pgl.unbindBackBufferTex(); + pgl.unbindFrontTexture(); } else { - + ptexture.unbind(); } } @@ -5965,19 +5732,49 @@ public class PGraphicsOpenGL extends PGraphics { protected void initPrimary() { - pgl.initPrimarySurface(quality); + pgl.initSurface(quality); if (pgPrimary == null) { pgPrimary = this; } + if (texture != null) { + pgPrimary.removeCache(this); + texture = ptexture = null; + } + } + + + protected void updatePrimary() { + if (drawFramebuffer == null) { + drawFramebuffer = new FrameBuffer(parent, width, height, true); + setFramebuffer(drawFramebuffer); + } + if (readFramebuffer == null) { + readFramebuffer = new FrameBuffer(parent, width, height, true); + } + + drawFramebuffer.setFBO(pgl.getDrawFramebuffer()); + readFramebuffer.setFBO(pgl.getReadFramebuffer()); + if (pgl.isFBOBacked()) { + if (texture == null) { + texture = pgl.wrapBackTexture(); + } else { + texture.glName = pgl.getBackTextureName(); + } + if (ptexture == null) { + ptexture = pgl.wrapFrontTexture(); + } else { + ptexture.glName = pgl.getFrontTextureName(); + } + } + + pgl.update(); } protected void initOffscreen() { // Getting the context and capabilities from the main renderer. pgPrimary = (PGraphicsOpenGL)parent.g; - pgl.initOffscreenSurface(pgPrimary.pgl); - pgl.updateOffscreen(pgPrimary.pgl); - + pgl.initialized = true; loadTextureImpl(Texture.BILINEAR, false); // In case of reinitialization (for example, when the smooth level @@ -5986,18 +5783,18 @@ public class PGraphicsOpenGL extends PGraphics { if (offscreenFramebuffer != null) { offscreenFramebuffer.release(); } - if (offscreenFramebufferMultisample != null) { - offscreenFramebufferMultisample.release(); + if (multisampleFramebuffer != null) { + multisampleFramebuffer.release(); } boolean packed = depthBits == 24 && stencilBits == 8 && packedDepthStencilSupported; if (PGraphicsOpenGL.fboMultisampleSupported && 1 < quality) { - offscreenFramebufferMultisample = + multisampleFramebuffer = new FrameBuffer(parent, texture.glWidth, texture.glHeight, quality, 0, depthBits, stencilBits, packed, false); - offscreenFramebufferMultisample.clear(); + multisampleFramebuffer.clear(); offscreenMultisample = true; // The offscreen framebuffer where the multisampled image is finally drawn @@ -6020,6 +5817,195 @@ public class PGraphicsOpenGL extends PGraphics { } + protected void updateOffscreen() { + if (!pgl.initialized) { + initOffscreen(); + } else { + boolean outdated = offscreenFramebuffer != null && + offscreenFramebuffer.contextIsOutdated(); + boolean outdatedMulti = multisampleFramebuffer != null && + multisampleFramebuffer.contextIsOutdated(); + if (outdated || outdatedMulti) { + restartPGL(); + initOffscreen(); + } else { + // The back texture of the past frame becomes the front, + // and the front texture becomes the new back texture where the + // new frame is drawn to. + swapTextures(); + } + } + + pushFramebuffer(); + if (offscreenMultisample) { + setFramebuffer(multisampleFramebuffer); + } else { + setFramebuffer(offscreenFramebuffer); + } + } + + + protected void beginOffscreenDraw() { + // Just in case the texture was recreated (in a resize event for example) + offscreenFramebuffer.setColorBuffer(texture); + + // Restoring the clipping configuration of the offscreen surface. + if (clip) { + pgl.enable(PGL.SCISSOR_TEST); + pgl.scissor(clipRect[0], clipRect[1], clipRect[2], clipRect[3]); + } else { + pgl.disable(PGL.SCISSOR_TEST); + } + } + + + protected void endOffscreenDraw() { + if (offscreenMultisample) { + multisampleFramebuffer.copy(offscreenFramebuffer, currentFramebuffer); + } + + if (!clearColorBuffer0) { + // Draw the back texture into the front texture, which will be used as + // front texture in the next frame. Otherwise flickering will occur if + // the sketch uses "incremental drawing" (background() not called). + if (offscreenMultisample) { + pushFramebuffer(); + setFramebuffer(offscreenFramebuffer); + } + offscreenFramebuffer.setColorBuffer(ptexture); + drawTexture(); + offscreenFramebuffer.setColorBuffer(texture); + if (offscreenMultisample) { + popFramebuffer(); + } + } + + popFramebuffer(); + texture.updateTexels(); // Mark all texels in screen texture as modified. + + pgPrimary.restoreGL(); + } + + + protected void setDefaults() { + inGeo.clear(); + tessGeo.clear(); + texCache.clear(); + + // Each frame starts with textures disabled. + super.noTexture(); + + // Screen blend is needed for alpha (i.e. fonts) to work. + // Using setDefaultBlend() instead of blendMode() because + // the latter will set the blend mode only if it is different + // from current. + setDefaultBlend(); + + // this is necessary for 3D drawing + if (hints[DISABLE_DEPTH_TEST]) { + pgl.disable(PGL.DEPTH_TEST); + } else { + pgl.enable(PGL.DEPTH_TEST); + } + // use <= since that's what processing.core does + pgl.depthFunc(PGL.LEQUAL); + + if (hints[ENABLE_ACCURATE_2D]) { + flushMode = FLUSH_CONTINUOUSLY; + } else { + flushMode = FLUSH_WHEN_FULL; + } + + if (primarySurface) { + int[] temp = new int[1]; + pgl.getIntegerv(PGL.SAMPLES, temp, 0); + if (quality != temp[0] && 1 < temp[0] && 1 < quality) { + quality = temp[0]; + } + } + if (quality < 2) { + pgl.disable(PGL.MULTISAMPLE); + } else { + pgl.enable(PGL.MULTISAMPLE); + } + pgl.disable(PGL.POINT_SMOOTH); + pgl.disable(PGL.LINE_SMOOTH); + pgl.disable(PGL.POLYGON_SMOOTH); + + // setup opengl viewport. + viewport[0] = 0; viewport[1] = 0; viewport[2] = width; viewport[3] = height; + pgl.viewport(viewport[0], viewport[1], viewport[2], viewport[3]); + + if (sized) { + // To avoid having garbage in the screen after a resize, + // in the case background is not called in draw(). + background(backgroundColor); + + // Sets the default projection and camera (initializes modelview). + // If the user has setup up their own projection, they'll need + // to fix it after resize anyway. This helps the people who haven't + // set up their own projection. + defaultPerspective(); + defaultCamera(); + + // clear the flag + sized = false; + } else { + // Eliminating any user's transformations by going back to the + // original camera setup. + modelview.set(camera); + modelviewInv.set(cameraInv); + calcProjmodelview(); + } + + if (is3D()) { + noLights(); + lightFalloff(1, 0, 0); + lightSpecular(0, 0, 0); + } + + // Because y is flipped, the vertices that should be specified by + // the user in CCW order to define a front-facing facet, end up being CW. + pgl.frontFace(PGL.CW); + pgl.disable(PGL.CULL_FACE); + + // Processing uses only one texture unit. + pgl.activeTexture(PGL.TEXTURE0); + + // The current normal vector is set to be parallel to the Z axis. + normalX = normalY = normalZ = 0; + + // Clear depth and stencil buffers. + pgl.depthMask(true); + pgl.clearDepth(1); + pgl.clearStencil(0); + pgl.clear(PGL.DEPTH_BUFFER_BIT | PGL.STENCIL_BUFFER_BIT); + + if (!settingsInited) { + defaultSettings(); + } + + if (restoreSurface) { + restoreSurfaceFromPixels(); + restoreSurface = false; + } + + if (hints[DISABLE_DEPTH_MASK]) { + pgl.depthMask(false); + } else { + pgl.depthMask(true); + } + + pixelsOp = OP_NONE; + + modified = false; + setgetPixels = false; + + clearColorBuffer0 = clearColorBuffer; + clearColorBuffer = false; + } + + protected void getGLParameters() { OPENGL_VENDOR = pgl.getString(PGL.VENDOR); OPENGL_RENDERER = pgl.getString(PGL.RENDERER); @@ -6035,8 +6021,8 @@ public class PGraphicsOpenGL extends PGraphics { OPENGL_EXTENSIONS.indexOf("_shader_objects") == -1 || OPENGL_EXTENSIONS.indexOf("_shading_language") == -1) { // GLSL extensions are not present, we cannot do anything else here. - throw new RuntimeException("GLSL shaders are not supported by this " + - "video card"); + throw new RuntimeException("Processing cannot run because GLSL shaders" + + " are not available."); } } @@ -6048,6 +6034,8 @@ public class PGraphicsOpenGL extends PGraphics { -1 < OPENGL_EXTENSIONS.indexOf("_framebuffer_multisample"); packedDepthStencilSupported = -1 < OPENGL_EXTENSIONS.indexOf("_packed_depth_stencil"); + anisoSamplingSupported = + -1 < OPENGL_EXTENSIONS.indexOf("_texture_filter_anisotropic"); try { pgl.blendEquation(PGL.FUNC_ADD); @@ -6058,6 +6046,9 @@ public class PGraphicsOpenGL extends PGraphics { int temp[] = new int[2]; + depthBits = pgl.getDepthBits(); + stencilBits = pgl.getStencilBits(); + pgl.getIntegerv(PGL.MAX_TEXTURE_SIZE, temp, 0); maxTextureSize = temp[0]; @@ -6070,12 +6061,6 @@ public class PGraphicsOpenGL extends PGraphics { pgl.getIntegerv(PGL.ALIASED_POINT_SIZE_RANGE, temp, 0); maxPointSize = temp[1]; - pgl.getIntegerv(PGL.DEPTH_BITS, temp, 0); - depthBits = temp[0]; - - pgl.getIntegerv(PGL.STENCIL_BITS, temp, 0); - stencilBits = temp[0]; - if (anisoSamplingSupported) { float ftemp[] = new float[1]; pgl.getFloatv(PGL.MAX_TEXTURE_MAX_ANISOTROPY, ftemp, 0); @@ -6103,8 +6088,7 @@ public class PGraphicsOpenGL extends PGraphics { shader.setVertexShader(defPolyColorShaderVertURL); } if (shader == null){ - PGraphics.showWarning("The GLSL code doesn't seem to contain a valid " + - "shader to use in Processing."); + PGraphics.showWarning(INVALID_PROCESSING_SHADER_ERROR); } else { shader.setFragmentShader(fragFilename); } @@ -6155,8 +6139,7 @@ public class PGraphicsOpenGL extends PGraphics { } } if (shader == null) { - PGraphics.showWarning("The GLSL code doesn't seem to contain a valid " + - "shader to use in Processing."); + PGraphics.showWarning(INVALID_PROCESSING_SHADER_ERROR); } return shader; } @@ -6182,22 +6165,22 @@ public class PGraphicsOpenGL extends PGraphics { } else if (shader instanceof PolyLightShader) { polyLightShader = (PolyLightShader) shader; } else { - showWarning("shader() called with a wrong shader object"); + PGraphics.showWarning(WRONG_SHADER_TYPE_ERROR); } } else if (kind == LINES) { if (shader instanceof LineShader) { lineShader = (LineShader)shader; } else { - showWarning("shader() called with a wrong shader object"); + PGraphics.showWarning(WRONG_SHADER_TYPE_ERROR); } } else if (kind == POINTS) { if (shader instanceof PointShader) { pointShader = (PointShader)shader; } else { - showWarning("shader() called with a wrong shader object"); + PGraphics.showWarning(WRONG_SHADER_TYPE_ERROR); } } else { - showWarning("shader() called with an unknown shader type"); + PGraphics.showWarning(UNKNOWN_SHADER_KIND_ERROR); } } @@ -6222,7 +6205,7 @@ public class PGraphicsOpenGL extends PGraphics { } else if (kind == POINTS) { pointShader = null; } else { - PGraphics.showWarning("Wrong shader type"); + PGraphics.showWarning(UNKNOWN_SHADER_KIND_ERROR); } } @@ -6286,8 +6269,8 @@ public class PGraphicsOpenGL extends PGraphics { } - protected PolyShader getPolyShader(boolean lit, boolean tex) { - PolyShader shader; + protected BaseShader getPolyShader(boolean lit, boolean tex) { + BaseShader shader; if (lit) { if (tex) { if (polyTexlightShader == null) { @@ -6353,8 +6336,7 @@ public class PGraphicsOpenGL extends PGraphics { (polyLightShader != null || polyTexShader != null || polyColorShader != null)) { - PGraphics.showWarning("Your shader cannot be used to render textured " + - "and lit geometry, using default shader instead."); + PGraphics.showWarning(NO_TEXLIGHT_SHADER_ERROR); } } @@ -6364,8 +6346,7 @@ public class PGraphicsOpenGL extends PGraphics { (polyTexlightShader != null || polyTexShader != null || polyColorShader != null)) { - PGraphics.showWarning("Your shader cannot be used to render lit " + - "geometry, using default shader instead."); + PGraphics.showWarning(NO_LIGHT_SHADER_ERROR); } } @@ -6375,8 +6356,7 @@ public class PGraphicsOpenGL extends PGraphics { (polyTexlightShader != null || polyLightShader != null || polyColorShader != null)) { - PGraphics.showWarning("Your shader cannot be used to render textured " + - "geometry, using default shader instead."); + PGraphics.showWarning(NO_TEXTURE_SHADER_ERROR); } } @@ -6386,8 +6366,7 @@ public class PGraphicsOpenGL extends PGraphics { (polyTexlightShader != null || polyLightShader != null || polyTexShader != null)) { - PGraphics.showWarning("Your shader cannot be used to render colored " + - "geometry, using default shader instead."); + PGraphics.showWarning(NO_COLOR_SHADER_ERROR); } } @@ -6428,19 +6407,111 @@ public class PGraphicsOpenGL extends PGraphics { } - protected class PolyShader extends PShader { - public PolyShader(PApplet parent) { + protected class BaseShader extends PShader { + protected int projmodelviewMatrixLoc; + protected int modelviewMatrixLoc; + protected int projectionMatrixLoc; + protected int pframeSamplerLoc; + protected int resolutionLoc; + protected int viewportLoc; + protected int mouseLoc; + protected int pmouseLoc; + protected int timeLoc; + + public BaseShader(PApplet parent) { super(parent); } - public PolyShader(PApplet parent, String vertFilename, String fragFilename) { + public BaseShader(PApplet parent, String vertFilename, String fragFilename) { super(parent, vertFilename, fragFilename); } - public PolyShader(PApplet parent, URL vertURL, URL fragURL) { + public BaseShader(PApplet parent, URL vertURL, URL fragURL) { super(parent, vertURL, fragURL); } + @Override + public void loadUniforms() { + projmodelviewMatrixLoc = getUniformLoc("projmodelviewMatrix"); + modelviewMatrixLoc = getUniformLoc("modelviewMatrix"); + projectionMatrixLoc = getUniformLoc("projectionMatrix"); + + resolutionLoc = getUniformLoc("resolution"); + viewportLoc = getUniformLoc("viewport"); + mouseLoc = getUniformLoc("mouse"); + pmouseLoc = getUniformLoc("pmouse"); + timeLoc = getUniformLoc("time"); + + pframeSamplerLoc = getUniformLoc("pframeSampler"); + } + + @Override + public void unbind() { + if (-1 < pframeSamplerLoc) { + pgl.activeTexture(PGL.TEXTURE0 + lastTexUnit); + pgCurrent.unbindBackTexture(); + pgl.activeTexture(PGL.TEXTURE0); + } + + pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); + + super.unbind(); + } + + protected void setCommonUniforms() { + if (-1 < projmodelviewMatrixLoc) { + pgCurrent.updateGLProjmodelview(); + setUniformMatrix(projmodelviewMatrixLoc, pgCurrent.glProjmodelview); + } + + if (-1 < modelviewMatrixLoc) { + pgCurrent.updateGLModelview(); + setUniformMatrix(modelviewMatrixLoc, pgCurrent.glModelview); + } + + if (-1 < projectionMatrixLoc) { + pgCurrent.updateGLProjection(); + setUniformMatrix(projectionMatrixLoc, pgCurrent.glProjection); + } + + if (1 < resolutionLoc) { + float w = pgCurrent.width; + float h = pgCurrent.height; + setUniformValue(resolutionLoc, w, h); + } + + if (-1 < viewportLoc) { + float x = pgCurrent.viewport[0]; + float y = pgCurrent.viewport[1]; + float w = pgCurrent.viewport[2]; + float h = pgCurrent.viewport[3]; + setUniformValue(viewportLoc, x, y, w, h); + } + + if (-1 < mouseLoc) { + float mx = pgCurrent.parent.mouseX; + float my = pgCurrent.parent.height - pgCurrent.parent.mouseY; + setUniformValue(mouseLoc, mx, my); + } + + if (-1 < pmouseLoc) { + float pmx = pgCurrent.parent.pmouseX; + float pmy = pgCurrent.parent.height - pgCurrent.parent.pmouseY; + setUniformValue(pmouseLoc, pmx, pmy); + } + + if (-1 < timeLoc) { + float sec = pgCurrent.parent.millis() / 1000.0f; + setUniformValue(timeLoc, sec); + } + + if (-1 < pframeSamplerLoc) { + setUniformValue(pframeSamplerLoc, lastTexUnit); + pgl.activeTexture(PGL.TEXTURE0 + lastTexUnit); + pgCurrent.bindBackTexture(); + } + } + public void setVertexAttribute(int vboId, int size, int type, int stride, int offset) { } public void setColorAttribute(int vboId, int size, int type, @@ -6461,13 +6532,7 @@ public class PGraphicsOpenGL extends PGraphics { } - protected class PolyColorShader extends PolyShader { - protected int projmodelviewMatrixLoc; - protected int modelviewMatrixLoc; - protected int projectionMatrixLoc; - protected int backbufferSamplerLoc; - protected int resolutionLoc; - + protected class PolyColorShader extends BaseShader { protected int inVertexLoc; protected int inColorLoc; @@ -6492,12 +6557,7 @@ public class PGraphicsOpenGL extends PGraphics { @Override public void loadUniforms() { - projmodelviewMatrixLoc = getUniformLoc("projmodelviewMatrix"); - modelviewMatrixLoc = getUniformLoc("modelviewMatrix"); - projectionMatrixLoc = getUniformLoc("projectionMatrix"); - - backbufferSamplerLoc = getUniformLoc("backbufferSampler"); - resolutionLoc = getUniformLoc("resolution"); + super.loadUniforms(); } @Override @@ -6524,30 +6584,7 @@ public class PGraphicsOpenGL extends PGraphics { if (-1 < inVertexLoc) pgl.enableVertexAttribArray(inVertexLoc); if (-1 < inColorLoc) pgl.enableVertexAttribArray(inColorLoc); - if (-1 < projmodelviewMatrixLoc) { - pgCurrent.updateGLProjmodelview(); - setUniformMatrix(projmodelviewMatrixLoc, pgCurrent.glProjmodelview); - } - - if (-1 < modelviewMatrixLoc) { - pgCurrent.updateGLModelview(); - setUniformMatrix(modelviewMatrixLoc, pgCurrent.glModelview); - } - - if (-1 < projectionMatrixLoc) { - pgCurrent.updateGLProjection(); - setUniformMatrix(projectionMatrixLoc, pgCurrent.glProjection); - } - - float w = pgCurrent.width; - float h = pgCurrent.height; - setUniformValue(resolutionLoc, w, h); - - if (-1 < backbufferSamplerLoc) { - setUniformValue(backbufferSamplerLoc, lastTexUnit); - pgl.activeTexture(PGL.TEXTURE0 + lastTexUnit); - pgCurrent.bindBackTexture(); - } + setCommonUniforms(); } @Override @@ -6555,23 +6592,12 @@ public class PGraphicsOpenGL extends PGraphics { if (-1 < inVertexLoc) pgl.disableVertexAttribArray(inVertexLoc); if (-1 < inColorLoc) pgl.disableVertexAttribArray(inColorLoc); - if (-1 < backbufferSamplerLoc) { - pgl.activeTexture(PGL.TEXTURE0 + lastTexUnit); - pgCurrent.unbindBackTexture(); - pgl.activeTexture(PGL.TEXTURE0); - } - - pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - super.unbind(); } } - protected class PolyLightShader extends PolyShader { - protected int projmodelviewMatrixLoc; - protected int modelviewMatrixLoc; - protected int projectionMatrixLoc; + protected class PolyLightShader extends BaseShader { protected int normalMatrixLoc; protected int lightCountLoc; @@ -6619,9 +6645,8 @@ public class PGraphicsOpenGL extends PGraphics { @Override public void loadUniforms() { - projmodelviewMatrixLoc = getUniformLoc("projmodelviewMatrix"); - modelviewMatrixLoc = getUniformLoc("modelviewMatrix"); - projectionMatrixLoc = getUniformLoc("projectionMatrix"); + super.loadUniforms(); + normalMatrixLoc = getUniformLoc("normalMatrix"); lightCountLoc = getUniformLoc("lightCount"); @@ -6694,21 +6719,6 @@ public class PGraphicsOpenGL extends PGraphics { if (-1 < inEmissiveLoc) pgl.enableVertexAttribArray(inEmissiveLoc); if (-1 < inShineLoc) pgl.enableVertexAttribArray(inShineLoc); - if (-1 < projmodelviewMatrixLoc) { - pgCurrent.updateGLProjmodelview(); - setUniformMatrix(projmodelviewMatrixLoc, pgCurrent.glProjmodelview); - } - - if (-1 < modelviewMatrixLoc) { - pgCurrent.updateGLModelview(); - setUniformMatrix(modelviewMatrixLoc, pgCurrent.glModelview); - } - - if (-1 < projectionMatrixLoc) { - pgCurrent.updateGLProjection(); - setUniformMatrix(projectionMatrixLoc, pgCurrent.glProjection); - } - if (-1 < normalMatrixLoc) { pgCurrent.updateGLNormal(); setUniformMatrix(normalMatrixLoc, pgCurrent.glNormal); @@ -6725,6 +6735,8 @@ public class PGraphicsOpenGL extends PGraphics { pgCurrent.lightFalloffCoefficients, 3, count); setUniformVector(lightSpotParametersLoc, pgCurrent.lightSpotParameters, 2, count); + + setCommonUniforms(); } @Override @@ -6738,8 +6750,6 @@ public class PGraphicsOpenGL extends PGraphics { if (-1 < inEmissiveLoc) pgl.disableVertexAttribArray(inEmissiveLoc); if (-1 < inShineLoc) pgl.disableVertexAttribArray(inShineLoc); - pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - super.unbind(); } } @@ -6945,12 +6955,7 @@ public class PGraphicsOpenGL extends PGraphics { } - protected class LineShader extends PShader { - protected int projmodelviewMatrixLoc; - protected int modelviewMatrixLoc; - protected int projectionMatrixLoc; - - protected int viewportLoc; + protected class LineShader extends BaseShader { protected int perspectiveLoc; protected int scaleLoc; @@ -6980,20 +6985,20 @@ public class PGraphicsOpenGL extends PGraphics { @Override public void loadUniforms() { - projmodelviewMatrixLoc = getUniformLoc("projmodelviewMatrix"); - modelviewMatrixLoc = getUniformLoc("modelviewMatrix"); - projectionMatrixLoc = getUniformLoc("projectionMatrix"); + super.loadUniforms(); viewportLoc = getUniformLoc("viewport"); perspectiveLoc = getUniformLoc("perspective"); scaleLoc = getUniformLoc("scale"); } + @Override public void setVertexAttribute(int vboId, int size, int type, int stride, int offset) { setAttributeVBO(inVertexLoc, vboId, size, type, false, stride, offset); } + @Override public void setColorAttribute(int vboId, int size, int type, int stride, int offset) { setAttributeVBO(inColorLoc, vboId, size, type, true, stride, offset); @@ -7017,27 +7022,6 @@ public class PGraphicsOpenGL extends PGraphics { if (-1 < inColorLoc) pgl.enableVertexAttribArray(inColorLoc); if (-1 < inAttribLoc) pgl.enableVertexAttribArray(inAttribLoc); - if (-1 < projmodelviewMatrixLoc) { - pgCurrent.updateGLProjmodelview(); - setUniformMatrix(projmodelviewMatrixLoc, pgCurrent.glProjmodelview); - } - - if (-1 < modelviewMatrixLoc) { - pgCurrent.updateGLModelview(); - setUniformMatrix(modelviewMatrixLoc, pgCurrent.glModelview); - } - - if (-1 < projectionMatrixLoc) { - pgCurrent.updateGLProjection(); - setUniformMatrix(projectionMatrixLoc, pgCurrent.glProjection); - } - - float x = pgCurrent.viewport[0]; - float y = pgCurrent.viewport[1]; - float w = pgCurrent.viewport[2]; - float h = pgCurrent.viewport[3]; - setUniformValue(viewportLoc, x, y, w, h); - if (pgCurrent.getHint(ENABLE_STROKE_PERSPECTIVE) && !pgCurrent.usingOrthoProjection) { setUniformValue(perspectiveLoc, 1); @@ -7054,6 +7038,8 @@ public class PGraphicsOpenGL extends PGraphics { setUniformValue(scaleLoc, 0.99f, 0.99f, 0.99f); } } + + setCommonUniforms(); } @Override @@ -7062,19 +7048,12 @@ public class PGraphicsOpenGL extends PGraphics { if (-1 < inColorLoc) pgl.disableVertexAttribArray(inColorLoc); if (-1 < inAttribLoc) pgl.disableVertexAttribArray(inAttribLoc); - pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - super.unbind(); } } - protected class PointShader extends PShader { - protected int projmodelviewMatrixLoc; - protected int modelviewMatrixLoc; - protected int projectionMatrixLoc; - - protected int viewportLoc; + protected class PointShader extends BaseShader { protected int perspectiveLoc; protected int inVertexLoc; @@ -7103,19 +7082,18 @@ public class PGraphicsOpenGL extends PGraphics { @Override public void loadUniforms() { - projmodelviewMatrixLoc = getUniformLoc("projmodelviewMatrix"); - modelviewMatrixLoc = getUniformLoc("modelviewMatrix"); - projectionMatrixLoc = getUniformLoc("projectionMatrix"); + super.loadUniforms(); - viewportLoc = getUniformLoc("viewport"); perspectiveLoc = getUniformLoc("perspective"); } + @Override public void setVertexAttribute(int vboId, int size, int type, int stride, int offset) { setAttributeVBO(inVertexLoc, vboId, size, type, false, stride, offset); } + @Override public void setColorAttribute(int vboId, int size, int type, int stride, int offset) { setAttributeVBO(inColorLoc, vboId, size, type, true, stride, offset); @@ -7139,33 +7117,14 @@ public class PGraphicsOpenGL extends PGraphics { if (-1 < inColorLoc) pgl.enableVertexAttribArray(inColorLoc); if (-1 < inPointLoc) pgl.enableVertexAttribArray(inPointLoc); - if (-1 < projmodelviewMatrixLoc) { - pgCurrent.updateGLProjmodelview(); - setUniformMatrix(projmodelviewMatrixLoc, pgCurrent.glProjmodelview); - } - - if (-1 < modelviewMatrixLoc) { - pgCurrent.updateGLModelview(); - setUniformMatrix(modelviewMatrixLoc, pgCurrent.glModelview); - } - - if (-1 < projectionMatrixLoc) { - pgCurrent.updateGLProjection(); - setUniformMatrix(projectionMatrixLoc, pgCurrent.glProjection); - } - - float x = pgCurrent.viewport[0]; - float y = pgCurrent.viewport[1]; - float w = pgCurrent.viewport[2]; - float h = pgCurrent.viewport[3]; - setUniformValue(viewportLoc, x, y, w, h); - if (pgCurrent.getHint(ENABLE_STROKE_PERSPECTIVE) && !pgCurrent.usingOrthoProjection) { setUniformValue(perspectiveLoc, 1); } else { setUniformValue(perspectiveLoc, 0); } + + super.setCommonUniforms(); } @Override @@ -7174,8 +7133,6 @@ public class PGraphicsOpenGL extends PGraphics { if (-1 < inColorLoc) pgl.disableVertexAttribArray(inColorLoc); if (-1 < inPointLoc) pgl.disableVertexAttribArray(inPointLoc); - pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - super.unbind(); } } @@ -8022,6 +7979,7 @@ public class PGraphicsOpenGL extends PGraphics { boolean fill, boolean stroke, int detail, int code, int shape) { curveVertexCheck(shape); + float[] vertex = curveVertices[curveVertexCount]; vertex[X] = x; vertex[Y] = y; @@ -10685,6 +10643,9 @@ public class PGraphicsOpenGL extends PGraphics { void tessellateEdges() { if (stroke) { + if (in.edgeCount == 0) { + return; + } if (is3D) { tessellateEdges3D(); } else if (is2D) { @@ -10821,8 +10782,7 @@ public class PGraphicsOpenGL extends PGraphics { tess.setLineVertex(vidx, in, i0, color0); if (newCache) { - PGraphics.showWarning("Stroke path is too long, some bevel " + - "triangles won't be added."); + PGraphics.showWarning(TOO_LONG_STROKE_PATH_ERROR); // TODO: Fix this situation, the vertices from the previous cache // block should be copied in the newly created one. @@ -11043,11 +11003,11 @@ public class PGraphicsOpenGL extends PGraphics { rawIndices[idx++] = i0; rawIndices[idx++] = i1; - rawIndices[idx++] = i3; + rawIndices[idx++] = i2; - rawIndices[idx++] = i1; rawIndices[idx++] = i2; rawIndices[idx++] = i3; + rawIndices[idx++] = i0; } splitRawIndices(); } @@ -11629,7 +11589,7 @@ public class PGraphicsOpenGL extends PGraphics { public void error(int errnum) { String estring = pgl.tessError(errnum); - PGraphics.showWarning("Tessellation Error: " + estring); + PGraphics.showWarning(TESSELLATION_ERROR, estring); } /** @@ -11680,4 +11640,4 @@ public class PGraphicsOpenGL extends PGraphics { } } } -} \ No newline at end of file +} diff --git a/android/core/src/processing/opengl/PShader.java b/android/core/src/processing/opengl/PShader.java index 0a4827981..15c263cfb 100644 --- a/android/core/src/processing/opengl/PShader.java +++ b/android/core/src/processing/opengl/PShader.java @@ -34,6 +34,8 @@ import java.util.HashMap; * and a fragment shader. Based on the GLSLShader class from GLGraphics, which * in turn was originally based in the code by JohnG: * http://processing.org/discourse/beta/num_1159494801.html + * + * @webref rendering:shaders */ public class PShader { // shaders constants @@ -113,9 +115,9 @@ public class PShader { * Creates a shader program using the specified vertex and fragment * shaders. * - * @param parent PApplet - * @param vertexFN String - * @param fragmentFN String + * @param parent the parent program + * @param vertFilename name of the vertex shader + * @param fragFilename name of the fragment shader */ public PShader(PApplet parent, String vertFilename, String fragFilename) { this.parent = parent; @@ -132,7 +134,10 @@ public class PShader { glFragment = 0; } - + /** + * @param vertURL network location of the vertex shader + * @param fragURL network location of the fragment shader + */ public PShader(PApplet parent, URL vertURL, URL fragURL) { this.parent = parent; pgMain = (PGraphicsOpenGL) parent.g; @@ -216,22 +221,33 @@ public class PShader { return bound; } - + /** + * @webref rendering:shaders + * @brief Sets a variable within the shader + * @param name the name of the uniform variable to modify + * @param x first component of the variable to modify + */ public void set(String name, int x) { setUniformImpl(name, UniformValue.INT1, new int[] { x }); } - + /** + * @param y second component of the variable to modify. The variable has to be declared with an array/vector type in the shader (i.e.: int[2], vec2) + */ public void set(String name, int x, int y) { setUniformImpl(name, UniformValue.INT2, new int[] { x, y }); } - + /** + * @param z third component of the variable to modify. The variable has to be declared with an array/vector type in the shader (i.e.: int[3], vec3) + */ public void set(String name, int x, int y, int z) { setUniformImpl(name, UniformValue.INT3, new int[] { x, y, z }); } - + /** + * @param w fourth component of the variable to modify. The variable has to be declared with an array/vector type in the shader (i.e.: int[4], vec4) + */ public void set(String name, int x, int y, int z, int w) { setUniformImpl(name, UniformValue.INT4, new int[] { x, y, z }); } @@ -256,7 +272,9 @@ public class PShader { setUniformImpl(name, UniformValue.FLOAT4, new float[] { x, y, z, w }); } - + /** + * @param vec modifies all the components of an array/vector uniform variable. PVector can only be used if the type of the variable is vec3. + */ public void set(String name, PVector vec) { setUniformImpl(name, UniformValue.FLOAT3, new float[] { vec.x, vec.y, vec.z }); @@ -267,7 +285,9 @@ public class PShader { set(name, vec, 1); } - + /** + * @param ncoords number of coordinates per element, max 4 + */ public void set(String name, int[] vec, int ncoords) { if (ncoords == 1) { setUniformImpl(name, UniformValue.INT1VEC, vec); @@ -308,7 +328,9 @@ public class PShader { } } - + /** + * @param mat matrix of values + */ public void set(String name, PMatrix2D mat) { float[] matv = { mat.m00, mat.m01, mat.m10, mat.m11 }; @@ -320,7 +342,9 @@ public class PShader { set(name, mat, false); } - + /** + * @param use3x3 enforces the matrix is 3 x 3 + */ public void set(String name, PMatrix3D mat, boolean use3x3) { if (use3x3) { float[] matv = { mat.m00, mat.m01, mat.m02, @@ -336,7 +360,9 @@ public class PShader { } } - + /** + * @param tex sets the sampler uniform variable to read from this image texture + */ public void set(String name, PImage tex) { setUniformImpl(name, UniformValue.SAMPLER2D, tex); } diff --git a/android/core/src/processing/opengl/PShapeOpenGL.java b/android/core/src/processing/opengl/PShapeOpenGL.java index 6201f66a9..e308eb3ab 100644 --- a/android/core/src/processing/opengl/PShapeOpenGL.java +++ b/android/core/src/processing/opengl/PShapeOpenGL.java @@ -33,7 +33,7 @@ import processing.core.PShape; import processing.core.PVector; import processing.opengl.PGraphicsOpenGL.LineShader; import processing.opengl.PGraphicsOpenGL.PointShader; -import processing.opengl.PGraphicsOpenGL.PolyShader; +import processing.opengl.PGraphicsOpenGL.BaseShader; import processing.opengl.PGraphicsOpenGL.IndexCache; import processing.opengl.PGraphicsOpenGL.InGeometry; import processing.opengl.PGraphicsOpenGL.TessGeometry; @@ -350,6 +350,10 @@ public class PShapeOpenGL extends PShape { normalMode = NORMAL_MODE_AUTO; + // To make sure that the first vertex is marked as a break. + // Same behavior as in the immediate mode. + breakShape = true; + if (family == GROUP) { // GROUP shapes are always marked as ended. shapeEnded = true; @@ -895,6 +899,7 @@ public class PShapeOpenGL extends PShape { return; } openContour = true; + breakShape = true; } @@ -910,7 +915,6 @@ public class PShapeOpenGL extends PShape { return; } openContour = false; - breakShape = true; } @@ -4306,7 +4310,7 @@ public class PShapeOpenGL extends PShape { } boolean renderingFill = false, renderingStroke = false; - PolyShader shader = null; + BaseShader shader = null; IndexCache cache = tessGeo.polyIndexCache; for (int n = firstPolyIndexCache; n <= lastPolyIndexCache; n++) { if (is3D() || (tex != null && (firstLineIndexCache == -1 || diff --git a/android/core/src/processing/opengl/Texture.java b/android/core/src/processing/opengl/Texture.java index 3e241f784..f6708c7a5 100644 --- a/android/core/src/processing/opengl/Texture.java +++ b/android/core/src/processing/opengl/Texture.java @@ -1,1589 +1,1626 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2011-12 Ben Fry and Casey Reas - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General - Public License along with this library; if not, write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA -*/ - -package processing.opengl; - -import processing.core.PApplet; -import processing.core.PConstants; -import processing.core.PGraphics; -import processing.core.PImage; -import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.nio.IntBuffer; -import java.util.LinkedList; -import java.util.NoSuchElementException; - -/** - * This class wraps an OpenGL texture. - * By Andres Colubri - * - */ -public class Texture implements PConstants { - // texture constants - - /** - * Texture with normalized UV. - */ - protected static final int TEX2D = 0; - /** - * Texture with un-normalized UV. - */ - protected static final int TEXRECT = 1; - - /** Point sampling: both magnification and minification filtering are set - * to nearest */ - protected static final int POINT = 2; - /** Linear sampling: magnification filtering is nearest, minification set - * to linear */ - protected static final int LINEAR = 3; - /** Bilinear sampling: both magnification filtering is set to linear and - * minification either to linear-mipmap-nearest (linear interplation is used - * within a mipmap, but not between different mipmaps). */ - protected static final int BILINEAR = 4; - /** Trilinear sampling: magnification filtering set to linear, minification to - * linear-mipmap-linear, which offers the best mipmap quality since linear - * interpolation to compute the value in each of two maps and then - * interpolates linearly between these two value. */ - protected static final int TRILINEAR = 5; - - public int width, height; - - public int glName; - public int glTarget; - public int glFormat; - public int glMinFilter; - public int glMagFilter; - public int glWrapS; - public int glWrapT; - public int glWidth; - public int glHeight; - - protected PApplet parent; // The Processing applet - protected PGraphicsOpenGL pg; // The main renderer - protected PGL pgl; // The interface between Processing and OpenGL. - protected PGL.Context context; // The context that created this texture. - protected PGraphicsOpenGL pgDraw; // The main renderer is the color buffer of. - - protected boolean usingMipmaps; - protected boolean usingRepeat; - protected float maxTexcoordU; - protected float maxTexcoordV; - protected boolean bound; - - protected boolean invertedX; - protected boolean invertedY; - - protected FrameBuffer tempFbo = null; - - /** Modified portion of the texture */ - protected boolean modified; - protected int mx1, my1, mx2, my2; - - protected Object bufferSource; - protected LinkedList bufferCache = null; - protected Method disposeBufferMethod; - public static final int MAX_BUFFER_CACHE_SIZE = 3; - - //////////////////////////////////////////////////////////// - - // Constructors. - - - /** - * Creates an instance of PTexture with size width x height. The texture is - * initialized (empty) to that size. - * @param parent PApplet - * @param width int - * @param height int - */ - public Texture(PApplet parent, int width, int height) { - this(parent, width, height, new Parameters()); - } - - - /** - * Creates an instance of PTexture with size width x height and with the - * specified parameters. The texture is initialized (empty) to that size. - * @param parent PApplet - * @param width int - * @param height int - * @param params Parameters - */ - public Texture(PApplet parent, int width, int height, Object params) { - this.parent = parent; - - pg = (PGraphicsOpenGL)parent.g; - pgl = pg.pgl; - context = pgl.createEmptyContext(); - - pgDraw = null; - - glName = 0; - - init(width, height, (Parameters)params); - } - - - @Override - protected void finalize() throws Throwable { - try { - if (glName != 0) { - pg.finalizeTextureObject(glName, context.id()); - } - } finally { - super.finalize(); - } - } - - - //////////////////////////////////////////////////////////// - - // Init, resize methods - - - /** - * Sets the size of the image and texture to width x height. If the texture is - * already initialized, it first destroys the current OpenGL texture object - * and then creates a new one with the specified size. - * @param width int - * @param height int - */ - public void init(int width, int height) { - Parameters params; - if (0 < glName) { - // Re-initializing a pre-existing texture. - // We use the current parameters as default: - params = getParameters(); - } else { - // Just built-in default parameters otherwise: - params = new Parameters(); - } - init(width, height, params); - } - - - /** - * Sets the size of the image and texture to width x height, and the - * parameters of the texture to params. If the texture is already initialized, - * it first destroys the current OpenGL texture object and then creates a new - * one with the specified size. - * @param width int - * @param height int - * @param params GLTextureParameters - */ - public void init(int width, int height, Parameters params) { - setParameters(params); - setSize(width, height); - allocate(); - } - - - public void resize(int wide, int high) { - // Marking the texture object as finalized so it is deleted - // when creating the new texture. - release(); - - // Creating new texture with the appropriate size. - Texture tex = new Texture(parent, wide, high, getParameters()); - - // Copying the contents of this texture into tex. - tex.set(this); - - // Now, overwriting "this" with tex. - copyObject(tex); - - // Nullifying some utility objects so they are recreated with the - // appropriate size when needed. - tempFbo = null; - } - - - /** - * Returns true if the texture has been initialized. - * @return boolean - */ - public boolean available() { - return 0 < glName; - } - - - //////////////////////////////////////////////////////////// - - // Set methods - - - public void set(PImage img) { - Texture tex = (Texture)pg.getCache(img); - set(tex); - } - - - public void set(PImage img, int x, int y, int w, int h) { - Texture tex = (Texture)pg.getCache(img); - set(tex, x, y, w, h); - } - - - public void set(Texture tex) { - copyTexture(tex, 0, 0, tex.width, tex.height, true); - } - - - public void set(Texture tex, int x, int y, int w, int h) { - copyTexture(tex, x, y, w, h, true); - } - - - public void set(int texTarget, int texName, int texWidth, int texHeight, - int w, int h) { - copyTexture(texTarget, texName, texWidth, texHeight, 0, 0, w, h, true); - } - - - public void set(int texTarget, int texName, int texWidth, int texHeight, - int target, int tex, int x, int y, int w, int h) { - copyTexture(texTarget, texName, texWidth, texHeight, x, y, w, h, true); - } - - - public void set(int[] pixels) { - set(pixels, 0, 0, width, height, ARGB); - } - - - public void set(int[] pixels, int format) { - set(pixels, 0, 0, width, height, format); - } - - - public void set(int[] pixels, int x, int y, int w, int h) { - set(pixels, x, y, w, h, ARGB); - } - - - public void set(int[] pixels, int x, int y, int w, int h, int format) { - if (pixels == null) { - pixels = null; - PGraphics.showWarning("The pixels array is null."); - return; - } - if (pixels.length != w * h) { - PGraphics.showWarning("The pixels array has a length of " + - pixels.length + ", but it should be " + w * h); - return; - } - - if (pixels.length == 0) { - // Nothing to do (means that w == h == 0) but not an erroneous situation - return; - } - - boolean enabledTex = false; - if (!pgl.texturingIsEnabled(glTarget)) { - pgl.enableTexturing(glTarget); - enabledTex = true; - } - pgl.bindTexture(glTarget, glName); - - if (usingMipmaps) { - if (PGraphicsOpenGL.autoMipmapGenSupported) { - // Automatic mipmap generation. - int[] rgbaPixels = new int[w * h]; - convertToRGBA(pixels, rgbaPixels, format, w, h); - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - IntBuffer.wrap(rgbaPixels)); - pgl.generateMipmap(glTarget); - rgbaPixels = null; - } else { - // TODO: finish manual mipmap generation, replacing Bitmap with AWT's BufferedImage, - // making it work in npot textures (embed npot tex into larger pot tex?), subregions, - // and moving GLUtils.texImage2D (originally from Android SDK) into PGL. - // Actually, this whole code should go into PGL, so the Android implementation can - // use Bitmap, and desktop use BufferedImage. - - /* - if (w != width || h != height) { - System.err.println("Sorry but I don't know how to generate mipmaps for a subregion."); - return; - } - - // Code by Mike Miller obtained from here: - // http://insanitydesign.com/wp/2009/08/01/android-opengl-es-mipmaps/ - int w0 = glWidth; - int h0 = glHeight; - int[] argbPixels = new int[w0 * h0]; - convertToARGB(pixels, argbPixels, format); - int level = 0; - int denom = 1; - - // We create a Bitmap because then we use its built-in filtered downsampling - // functionality. - Bitmap bitmap = Bitmap.createBitmap(w0, h0, Config.ARGB_8888); - bitmap.setPixels(argbPixels, 0, w0, 0, 0, w0, h0); - - while (w0 >= 1 || h0 >= 1) { - //First of all, generate the texture from our bitmap and set it to the according level - GLUtils.texImage2D(glTarget, level, bitmap, 0); - - // We are done. - if (w0 == 1 && h0 == 1) { - break; - } - - // Increase the mipmap level - level++; - denom *= 2; - - // Downsampling bitmap. We must eventually arrive to the 1x1 level, - // and if the width and height are different, there will be a few 1D - // texture levels just before. - // This update formula also allows for NPOT resolutions. - w0 = PApplet.max(1, PApplet.floor((float)glWidth / denom)); - h0 = PApplet.max(1, PApplet.floor((float)glHeight / denom)); - // (see getScaledInstance in AWT Image) - Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, w0, h0, true); - - // Clean up - bitmap.recycle(); - bitmap = bitmap2; - } - */ - - int[] rgbaPixels = new int[w * h]; - convertToRGBA(pixels, rgbaPixels, format, w, h); - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - IntBuffer.wrap(rgbaPixels)); - rgbaPixels = null; - } - } else { - int[] rgbaPixels = new int[w * h]; - convertToRGBA(pixels, rgbaPixels, format, w, h); - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - IntBuffer.wrap(rgbaPixels)); - rgbaPixels = null; - } - - pgl.bindTexture(glTarget, 0); - if (enabledTex) { - pgl.disableTexturing(glTarget); - } - - updateTexels(x, y, w, h); - } - - - //////////////////////////////////////////////////////////// - - // Native set methods - - - public void setNative(int[] pixels) { - setNative(pixels, 0, 0, width, height); - } - - - public void setNative(int[] pixels, int x, int y, int w, int h) { - setNative(IntBuffer.wrap(pixels), x, y, w, h); - } - - - public void setNative(IntBuffer pixels, int x, int y, int w, int h) { - if (pixels == null) { - pixels = null; - PGraphics.showWarning("The pixel buffer is null."); - return; - } - if (pixels.capacity() != w * h) { - PGraphics.showWarning("The pixels array has a length of " + - pixels.capacity() + ", but it should be " + w * h); - return; - } - - if (pixels.capacity() == 0) { - // Nothing to do (means that w == h == 0) but not an erroneous situation - return; - } - - boolean enabledTex = false; - if (!pgl.texturingIsEnabled(glTarget)) { - pgl.enableTexturing(glTarget); - enabledTex = true; - } - pgl.bindTexture(glTarget, glName); - - if (usingMipmaps) { - if (PGraphicsOpenGL.autoMipmapGenSupported) { - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - pixels); - pgl.generateMipmap(glTarget); - } else { - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - pixels); - } - } else { - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - pixels); - } - - pgl.bindTexture(glTarget, 0); - if (enabledTex) { - pgl.disableTexturing(glTarget); - } - - updateTexels(x, y, w, h); - } - - - //////////////////////////////////////////////////////////// - - // Get methods - - - /** - * Copy texture to pixels. Involves video memory to main memory transfer (slow). - */ - public void get(int[] pixels) { - if (pixels == null) { - throw new RuntimeException("Trying to copy texture to null pixels array"); - } - if (pixels.length != width * height) { - throw new RuntimeException("Trying to copy texture to pixels array of " + - "wrong size"); - } - - if (tempFbo == null) { - tempFbo = new FrameBuffer(parent, glWidth, glHeight); - } - - // Attaching the texture to the color buffer of a FBO, binding the FBO and - // reading the pixels from the current draw buffer (which is the color - // buffer of the FBO). - tempFbo.setColorBuffer(this); - pg.pushFramebuffer(); - pg.setFramebuffer(tempFbo); - tempFbo.readPixels(); - pg.popFramebuffer(); - - tempFbo.getPixels(pixels); - convertToARGB(pixels); - - if (invertedX) flipArrayOnX(pixels, 1); - if (invertedY) flipArrayOnY(pixels, 1); - } - - - /** - * Copies the contents of the texture to the pixels array. - * @param pixels - */ - public void loadPixels(int[] pixels) { - if (hasBuffers()) { - // Updates the texture AND the pixels array of the image at the same time, - // getting the pixels directly from the buffer data (and thus avoiding - // expensive transfer between video and main memory). - bufferUpdate(pixels); - } - - if (isModified()) { - // Regular pixel copy from texture. - get(pixels); - } - - setModified(false); - } - - - //////////////////////////////////////////////////////////// - - // Put methods (the source texture is not resized to cover the entire - // destination). - - - public void put(Texture tex) { - copyTexture(tex, 0, 0, tex.width, tex.height, false); - } - - - public void put(Texture tex, int x, int y, int w, int h) { - copyTexture(tex, x, y, w, h, false); - } - - - public void put(int texTarget, int texName, int texWidth, int texHeight, - int w, int h) { - copyTexture(texTarget, texName, texWidth, texHeight, 0, 0, w, h, false); - } - - - public void put(int texTarget, int texName, int texWidth, int texHeight, - int target, int tex, int x, int y, int w, int h) { - copyTexture(texTarget, texName, texWidth, texHeight, x, y, w, h, false); - } - - - //////////////////////////////////////////////////////////// - - // Get OpenGL parameters - - - /** - * Returns true or false whether or not the texture is using mipmaps. - * @return boolean - */ - public boolean usingMipmaps() { - return usingMipmaps; - } - - - public void usingMipmaps(boolean mipmaps, int sampling) { - if (mipmaps) { - if (glMinFilter != PGL.LINEAR_MIPMAP_NEAREST && - glMinFilter != PGL.LINEAR_MIPMAP_LINEAR) { - if (sampling == POINT) { - glMagFilter = PGL.NEAREST; - glMinFilter = PGL.NEAREST; - } else if (sampling == LINEAR) { - glMagFilter = PGL.NEAREST; - glMinFilter = - PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; - } else if (sampling == BILINEAR) { - glMagFilter = PGL.LINEAR; - glMinFilter = - PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; - } else if (sampling == TRILINEAR) { - glMagFilter = PGL.LINEAR; - glMinFilter = - PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_LINEAR : PGL.LINEAR; - } else { - throw new RuntimeException("Unknown texture filtering mode"); - } - } - - usingMipmaps = true; - } else { - if (glMinFilter == PGL.LINEAR_MIPMAP_NEAREST || - glMinFilter == PGL.LINEAR_MIPMAP_LINEAR) { - glMinFilter = PGL.LINEAR; - } - usingMipmaps = false; - } - - bind(); - pgl.texParameteri(glTarget, PGL.TEXTURE_MIN_FILTER, glMinFilter); - pgl.texParameteri(glTarget, PGL.TEXTURE_MAG_FILTER, glMagFilter); - if (usingMipmaps) { - if (PGraphicsOpenGL.autoMipmapGenSupported) { - pgl.generateMipmap(glTarget); - } else { - // TODO: need manual generation here.. - } - } - unbind(); - } - - - /** - * Returns true or false whether or not the texture is using repeat wrap mode - * along either U or V directions. - * @return boolean - */ - public boolean usingRepeat() { - return usingRepeat; - } - - - public void usingRepeat(boolean repeat) { - if (repeat) { - glWrapS = PGL.REPEAT; - glWrapT = PGL.REPEAT; - usingRepeat = true; - } else { - glWrapS = PGL.CLAMP_TO_EDGE; - glWrapT = PGL.CLAMP_TO_EDGE; - usingRepeat = false; - } - - bind(); - pgl.texParameteri(glTarget, PGL.TEXTURE_WRAP_S, glWrapS); - pgl.texParameteri(glTarget, PGL.TEXTURE_WRAP_T, glWrapT); - unbind(); - } - - - /** - * Returns the maximum possible value for the texture coordinate U - * (horizontal). - * @return float - */ - public float maxTexcoordU() { - return maxTexcoordU; - } - - - /** - * Returns the maximum possible value for the texture coordinate V (vertical). - * @return float - */ - public float maxTexcoordV() { - return maxTexcoordV; - } - - - /** - * Returns true if the texture is inverted along the horizontal direction. - * @return boolean; - */ - public boolean invertedX() { - return invertedX; - } - - - /** - * Sets the texture as inverted or not along the horizontal direction. - * @param v boolean; - */ - public void invertedX(boolean v) { - invertedX = v; - } - - - /** - * Returns true if the texture is inverted along the vertical direction. - * @return boolean; - */ - public boolean invertedY() { - return invertedY; - } - - - /** - * Sets the texture as inverted or not along the vertical direction. - * @param v boolean; - */ - public void invertedY(boolean v) { - invertedY = v; - } - - - //////////////////////////////////////////////////////////// - - // Bind/unbind - - - public void bind() { - // Binding a texture automatically enables texturing for the - // texture target from that moment onwards. Unbinding the texture - // won't disable texturing. - if (!pgl.texturingIsEnabled(glTarget)) { - pgl.enableTexturing(glTarget); - } - pgl.bindTexture(glTarget, glName); - bound = true; - } - - - public void unbind() { - if (pgl.textureIsBound(glTarget, glName)) { - // We don't want to unbind another texture - // that might be bound instead of this one. - if (!pgl.texturingIsEnabled(glTarget)) { - pgl.enableTexturing(glTarget); - pgl.bindTexture(glTarget, 0); - pgl.disableTexturing(glTarget); - } else { - pgl.bindTexture(glTarget, 0); - } - } - bound = false; - } - - - public boolean bound() { - // A true result might not necessarily mean that texturing is enabled - // (a texture can be bound to the target, but texturing is disabled). - return bound; - } - - - ////////////////////////////////////////////////////////////// - - // Modified flag - - - public boolean isModified() { - return modified; - } - - - public void setModified() { - modified = true; - } - - - public void setModified(boolean m) { - modified = m; - } - - - public int getModifiedX1() { - return mx1; - } - - - public int getModifiedX2() { - return mx2; - } - - - public int getModifiedY1() { - return my1; - } - - - public int getModifiedY2() { - return my2; - } - - - public void updateTexels() { - updateTexelsImpl(0, 0, width, height); - } - - - public void updateTexels(int x, int y, int w, int h) { - updateTexelsImpl(x, y, w, h); - } - - - protected void updateTexelsImpl(int x, int y, int w, int h) { - int x2 = x + w; - int y2 = y + h; - - if (!modified) { - mx1 = PApplet.max(0, x); - mx2 = PApplet.min(width - 1, x2); - my1 = PApplet.max(0, y); - my2 = PApplet.min(height - 1, y2); - modified = true; - - } else { - if (x < mx1) mx1 = PApplet.max(0, x); - if (x > mx2) mx2 = PApplet.min(width - 1, x); - if (y < my1) my1 = PApplet.max(0, y); - if (y > my2) my2 = y; - - if (x2 < mx1) mx1 = PApplet.max(0, x2); - if (x2 > mx2) mx2 = PApplet.min(width - 1, x2); - if (y2 < my1) my1 = PApplet.max(0, y2); - if (y2 > my2) my2 = PApplet.min(height - 1, y2); - } - } - - - //////////////////////////////////////////////////////////// - - // Buffer sink interface. - - - public void setBufferSource(Object source) { - bufferSource = source; - getSourceMethods(); - } - - - public void copyBufferFromSource(Object natRef, ByteBuffer byteBuf, - int w, int h) { - if (bufferCache == null) { - bufferCache = new LinkedList(); - } - - if (bufferCache.size() + 1 <= MAX_BUFFER_CACHE_SIZE) { - bufferCache.add(new BufferData(natRef, byteBuf.asIntBuffer(), w, h)); - } else { - // The buffer cache reached the maximum size, so we just dispose - // the new buffer. - try { - disposeBufferMethod.invoke(bufferSource, new Object[] { natRef }); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - - public boolean hasBufferSource() { - return bufferSource != null; - } - - - public boolean hasBuffers() { - return bufferSource != null && bufferCache != null && - 0 < bufferCache.size(); - } - - - protected boolean bufferUpdate() { - BufferData data = null; - try { - data = bufferCache.remove(0); - } catch (NoSuchElementException ex) { - PGraphics.showWarning("Don't have pixel data to copy to texture"); - } - - if (data != null) { - if ((data.w != width) || (data.h != height)) { - init(data.w, data.h); - } - setNative(data.rgbBuf, 0, 0, width, height); - - data.dispose(); - - return true; - } else { - return false; - } - } - - - protected boolean bufferUpdate(int[] pixels) { - BufferData data = null; - try { - data = bufferCache.remove(0); - } catch (NoSuchElementException ex) { - PGraphics.showWarning("Don't have pixel data to copy to texture"); - } - - if (data != null) { - if ((data.w != width) || (data.h != height)) { - init(data.w, data.h); - } - setNative(data.rgbBuf, 0, 0, width, height); - - data.rgbBuf.get(pixels); - convertToARGB(pixels); - - data.dispose(); - - return true; - } else { - return false; - } - } - - - protected void getSourceMethods() { - try { - disposeBufferMethod = bufferSource.getClass(). - getMethod("disposeBuffer", new Class[] { Object.class }); - } catch (Exception e) { - throw new RuntimeException("Provided source object doesn't have a " + - "disposeBuffer method."); - } - } - - - //////////////////////////////////////////////////////////// - - // Utilities - - - /** - * Flips intArray along the X axis. - * @param intArray int[] - * @param mult int - */ - protected void flipArrayOnX(int[] intArray, int mult) { - int index = 0; - int xindex = mult * (width - 1); - for (int x = 0; x < width / 2; x++) { - for (int y = 0; y < height; y++) { - int i = index + mult * y * width; - int j = xindex + mult * y * width; - - for (int c = 0; c < mult; c++) { - int temp = intArray[i]; - intArray[i] = intArray[j]; - intArray[j] = temp; - - i++; - j++; - } - - } - index += mult; - xindex -= mult; - } - } - - - /** - * Flips intArray along the Y axis. - * @param intArray int[] - * @param mult int - */ - protected void flipArrayOnY(int[] intArray, int mult) { - int index = 0; - int yindex = mult * (height - 1) * width; - for (int y = 0; y < height / 2; y++) { - for (int x = 0; x < mult * width; x++) { - int temp = intArray[index]; - intArray[index] = intArray[yindex]; - intArray[yindex] = temp; - - index++; - yindex++; - } - yindex -= mult * width * 2; - } - } - - - /** - * Reorders a pixel array in the given format into the order required by - * OpenGL (RGBA). Both arrays are assumed to be of the same length. The width - * and height parameters are used in the YUV420 to RBGBA conversion. - * @param intArray int[] - * @param tIntArray int[] - * @param arrayFormat int - * @param w int - * @param h int - */ - protected void convertToRGBA(int[] intArray, int[] tIntArray, int arrayFormat, - int w, int h) { - if (PGL.BIG_ENDIAN) { - switch (arrayFormat) { - case ALPHA: - - // Converting from xxxA into RGBA. RGB is set to white - // (0xFFFFFF, i.e.: (255, 255, 255)) - for (int i = 0; i< intArray.length; i++) { - tIntArray[i] = 0xFFFFFF00 | intArray[i]; - } - break; - - case RGB: - - // Converting xRGB into RGBA. A is set to 0xFF (255, full opacity). - for (int i = 0; i< intArray.length; i++) { - int pixel = intArray[i]; - tIntArray[i] = (pixel << 8) | 0xFF; - } - break; - - case ARGB: - - // Converting ARGB into RGBA. Shifting RGB to 8 bits to the left, - // and bringing A to the first byte. - for (int i = 0; i< intArray.length; i++) { - int pixel = intArray[i]; - tIntArray[i] = (pixel << 8) | ((pixel >> 24) & 0xFF); - } - break; - } - - } else { - // LITTLE_ENDIAN - // ARGB native, and RGBA opengl means ABGR on windows - // for the most part just need to swap two components here - // the sun.cpu.endian here might be "false", oddly enough.. - // (that's why just using an "else", rather than check for "little") - - switch (arrayFormat) { - case ALPHA: - - // Converting xxxA into ARGB, with RGB set to white. - for (int i = 0; i< intArray.length; i++) { - tIntArray[i] = (intArray[i] << 24) | 0x00FFFFFF; - } - break; - - case RGB: - - // We need to convert xRGB into ABGR, - // so R and B must be swapped, and the x just made 0xFF. - for (int i = 0; i< intArray.length; i++) { - int pixel = intArray[i]; - tIntArray[i] = 0xFF000000 | - ((pixel & 0xFF) << 16) | - ((pixel & 0xFF0000) >> 16) | - (pixel & 0x0000FF00); - } - break; - - case ARGB: - - // We need to convert ARGB into ABGR, - // so R and B must be swapped, A and G just brought back in. - for (int i = 0; i < intArray.length; i++) { - int pixel = intArray[i]; - tIntArray[i] = ((pixel & 0xFF) << 16) | - ((pixel & 0xFF0000) >> 16) | - (pixel & 0xFF00FF00); - } - break; - - } - - } - } - - - /** - * Reorders an OpenGL pixel array (RGBA) into ARGB. The array must be - * of size width * height. - * @param intArray int[] - */ - protected void convertToARGB(int[] intArray) { - int t = 0; - int p = 0; - if (PGL.BIG_ENDIAN) { - - // RGBA to ARGB conversion: shifting RGB 8 bits to the right, - // and placing A 24 bits to the left. - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int pixel = intArray[p++]; - intArray[t++] = (pixel >> 8) | ((pixel << 24) & 0xFF000000); - } - } - - } else { - - // We have to convert ABGR into ARGB, so R and B must be swapped, - // A and G just brought back in. - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int pixel = intArray[p++]; - intArray[t++] = ((pixel & 0xFF) << 16) | - ((pixel & 0xFF0000) >> 16) | - (pixel & 0xFF00FF00); - - } - } - } - } - - - - /////////////////////////////////////////////////////////// - - // Allocate/release texture. - - - protected void setSize(int w, int h) { - width = w; - height = h; - - if (PGraphicsOpenGL.npotTexSupported) { - glWidth = w; - glHeight = h; - } else { - glWidth = PGL.nextPowerOfTwo(w); - glHeight = PGL.nextPowerOfTwo(h); - } - - if (glWidth > PGraphicsOpenGL.maxTextureSize || - glHeight > PGraphicsOpenGL.maxTextureSize) { - glWidth = glHeight = 0; - throw new RuntimeException("Image width and height cannot be" + - " larger than " + - PGraphicsOpenGL.maxTextureSize + - " with this graphics card."); - } - - // If non-power-of-two textures are not supported, and the specified width - // or height is non-power-of-two, then glWidth (glHeight) will be greater - // than w (h) because it is chosen to be the next power of two, and this - // quotient will give the appropriate maximum texture coordinate value given - // this situation. - maxTexcoordU = (float)width / glWidth; - maxTexcoordV = (float)height / glHeight; - } - - - /** - * Allocates the opengl texture object. - */ - protected void allocate() { - release(); // Just in the case this object is being re-allocated. - - boolean enabledTex = false; - if (!pgl.texturingIsEnabled(glTarget)) { - pgl.enableTexturing(glTarget); - enabledTex = true; - } - - context = pgl.getCurrentContext(); - glName = pg.createTextureObject(context.id()); - - pgl.bindTexture(glTarget, glName); - pgl.texParameteri(glTarget, PGL.TEXTURE_MIN_FILTER, glMinFilter); - pgl.texParameteri(glTarget, PGL.TEXTURE_MAG_FILTER, glMagFilter); - pgl.texParameteri(glTarget, PGL.TEXTURE_WRAP_S, glWrapS); - pgl.texParameteri(glTarget, PGL.TEXTURE_WRAP_T, glWrapT); - if (PGraphicsOpenGL.anisoSamplingSupported) { - pgl.texParameterf(glTarget, PGL.TEXTURE_MAX_ANISOTROPY, - PGraphicsOpenGL.maxAnisoAmount); - } - - // First, we use glTexImage2D to set the full size of the texture (glW/glH - // might be diff from w/h in the case that the GPU doesn't support NPOT - // textures) - pgl.texImage2D(glTarget, 0, glFormat, glWidth, glHeight, 0, - PGL.RGBA, PGL.UNSIGNED_BYTE, null); - - // Makes sure that the texture buffer in video memory doesn't contain - // any garbage. - pgl.initTexture(glTarget, PGL.RGBA, width, height); - - pgl.bindTexture(glTarget, 0); - if (enabledTex) { - pgl.disableTexturing(glTarget); - } - bound = false; - } - - - /** - * Marks the texture object for deletion. - */ - protected void release() { - if (glName != 0) { - pg.finalizeTextureObject(glName, context.id()); - glName = 0; - } - } - - - protected boolean contextIsOutdated() { - boolean outdated = !pgl.contextIsCurrent(context); - if (outdated) { - // Removing the texture object from the renderer's list so it - // doesn't get deleted by OpenGL. The texture object was - // automatically disposed when the old context was destroyed. - pg.removeTextureObject(glName, context.id()); - - // And then set the id to zero, so it doesn't try to be - // deleted when the object's finalizer is invoked by the GC. - glName = 0; - } - return outdated; - } - - - protected void colorBufferOf(PGraphicsOpenGL pgDraw) { - this.pgDraw = pgDraw; - } - - - protected boolean isColorBuffer() { - return pgDraw != null; - } - - - /////////////////////////////////////////////////////////// - - // Utilities. - - - // Copies source texture tex into this. - protected void copyTexture(Texture tex, int x, int y, int w, int h, - boolean scale) { - if (tex == null) { - throw new RuntimeException("Source texture is null"); - } - - if (tempFbo == null) { - tempFbo = new FrameBuffer(parent, glWidth, glHeight); - } - - // This texture is the color (destination) buffer of the FBO. - tempFbo.setColorBuffer(this); - tempFbo.disableDepthTest(); - - // FBO copy: - pg.pushFramebuffer(); - pg.setFramebuffer(tempFbo); - if (scale) { - // Rendering tex into "this", and scaling the source rectangle - // to cover the entire destination region. - pgl.drawTexture(tex.glTarget, tex.glName, tex.glWidth, tex.glHeight, - x, y, w, h, 0, 0, width, height); - - } else { - // Rendering tex into "this" but without scaling so the contents - // of the source texture fall in the corresponding texels of the - // destination. - pgl.drawTexture(tex.glTarget, tex.glName, tex.glWidth, tex.glHeight, - x, y, w, h, x, y, w, h); - } - pg.popFramebuffer(); - updateTexels(x, y, w, h); - } - - - // Copies source texture tex into this. - protected void copyTexture(int texTarget, int texName, - int texWidth, int texHeight, - int x, int y, int w, int h, boolean scale) { - if (tempFbo == null) { - tempFbo = new FrameBuffer(parent, glWidth, glHeight); - } - - // This texture is the color (destination) buffer of the FBO. - tempFbo.setColorBuffer(this); - tempFbo.disableDepthTest(); - - // FBO copy: - pg.pushFramebuffer(); - pg.setFramebuffer(tempFbo); - if (scale) { - // Rendering tex into "this", and scaling the source rectangle - // to cover the entire destination region. - pgl.drawTexture(texTarget, texName, texWidth, texHeight, - x, y, w, h, 0, 0, width, height); - - } else { - // Rendering tex into "this" but without scaling so the contents - // of the source texture fall in the corresponding texels of the - // destination. - pgl.drawTexture(texTarget, texName, texWidth, texHeight, - x, y, w, h, x, y, w, h); - } - pg.popFramebuffer(); - updateTexels(x, y, w, h); - } - - - protected void copyObject(Texture src) { - // The OpenGL texture of this object is replaced with the one from the - // source object, so we delete the former to avoid resource wasting. - release(); - - width = src.width; - height = src.height; - - parent = src.parent; - pg = src.pg; - - glName = src.glName; - glTarget = src.glTarget; - glFormat = src.glFormat; - glMinFilter = src.glMinFilter; - glMagFilter = src.glMagFilter; - - glWidth= src.glWidth; - glHeight = src.glHeight; - - usingMipmaps = src.usingMipmaps; - usingRepeat = src.usingRepeat; - maxTexcoordU = src.maxTexcoordU; - maxTexcoordV = src.maxTexcoordV; - - invertedX = src.invertedX; - invertedY = src.invertedY; - } - - - /////////////////////////////////////////////////////////// - - // Parameter handling - - - public Parameters getParameters() { - Parameters res = new Parameters(); - - if (glTarget == PGL.TEXTURE_2D) { - res.target = TEX2D; - } - - if (glFormat == PGL.RGB) { - res.format = RGB; - } else if (glFormat == PGL.RGBA) { - res.format = ARGB; - } else if (glFormat == PGL.ALPHA) { - res.format = ALPHA; - } - - if (glMagFilter == PGL.NEAREST && glMinFilter == PGL.NEAREST) { - res.sampling = POINT; - res.mipmaps = false; - } else if (glMagFilter == PGL.NEAREST && glMinFilter == PGL.LINEAR) { - res.sampling = LINEAR; - res.mipmaps = false; - } else if (glMagFilter == PGL.NEAREST && - glMinFilter == PGL.LINEAR_MIPMAP_NEAREST) { - res.sampling = LINEAR; - res.mipmaps = true; - } else if (glMagFilter == PGL.LINEAR && glMinFilter == PGL.LINEAR) { - res.sampling = BILINEAR; - res.mipmaps = false; - } else if (glMagFilter == PGL.LINEAR && - glMinFilter == PGL.LINEAR_MIPMAP_NEAREST) { - res.sampling = BILINEAR; - res.mipmaps = true; - } else if (glMagFilter == PGL.LINEAR && - glMinFilter == PGL.LINEAR_MIPMAP_LINEAR) { - res.sampling = TRILINEAR; - res.mipmaps = true; - } - - if (glWrapS == PGL.CLAMP_TO_EDGE) { - res.wrapU = CLAMP; - } else if (glWrapS == PGL.REPEAT) { - res.wrapU = REPEAT; - } - - if (glWrapT == PGL.CLAMP_TO_EDGE) { - res.wrapV = CLAMP; - } else if (glWrapT == PGL.REPEAT) { - res.wrapV = REPEAT; - } - - return res; - } - - - /** - * Sets texture target and internal format according to the target and - * type specified. - * @param target int - * @param params GLTextureParameters - */ - protected void setParameters(Parameters params) { - if (params.target == TEX2D) { - glTarget = PGL.TEXTURE_2D; - } else { - throw new RuntimeException("Unknown texture target"); - } - - if (params.format == RGB) { - glFormat = PGL.RGB; - } else if (params.format == ARGB) { - glFormat = PGL.RGBA; - } else if (params.format == ALPHA) { - glFormat = PGL.ALPHA; - } else { - throw new RuntimeException("Unknown texture format"); - } - - if (params.sampling == POINT) { - glMagFilter = PGL.NEAREST; - glMinFilter = PGL.NEAREST; - } else if (params.sampling == LINEAR) { - glMagFilter = PGL.NEAREST; - glMinFilter = params.mipmaps && PGL.MIPMAPS_ENABLED ? - PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; - } else if (params.sampling == BILINEAR) { - glMagFilter = PGL.LINEAR; - glMinFilter = params.mipmaps && PGL.MIPMAPS_ENABLED ? - PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; - } else if (params.sampling == TRILINEAR) { - glMagFilter = PGL.LINEAR; - glMinFilter = params.mipmaps && PGL.MIPMAPS_ENABLED ? - PGL.LINEAR_MIPMAP_LINEAR : PGL.LINEAR; - } else { - throw new RuntimeException("Unknown texture filtering mode"); - } - - if (params.wrapU == CLAMP) { - glWrapS = PGL.CLAMP_TO_EDGE; - } else if (params.wrapU == REPEAT) { - glWrapS = PGL.REPEAT; - } else { - throw new RuntimeException("Unknown wrapping mode"); - } - - if (params.wrapV == CLAMP) { - glWrapT = PGL.CLAMP_TO_EDGE; - } else if (params.wrapV == REPEAT) { - glWrapT = PGL.REPEAT; - } else { - throw new RuntimeException("Unknown wrapping mode"); - } - - usingMipmaps = glMinFilter == PGL.LINEAR_MIPMAP_NEAREST || - glMinFilter == PGL.LINEAR_MIPMAP_LINEAR; - - usingRepeat = glWrapS == PGL.REPEAT || glWrapT == PGL.REPEAT; - - invertedX = false; - invertedY = false; - } - - - /////////////////////////////////////////////////////////////////////////// - - // Parameters object - - - /** - * This class stores the parameters for a texture: target, internal format, - * minimization filter and magnification filter. - */ - static public class Parameters { - /** - * Texture target. - */ - public int target; - - /** - * Texture internal format. - */ - public int format; - - /** - * Texture filtering (POINT, LINEAR, BILINEAR or TRILINEAR). - */ - public int sampling; - - /** - * Use mipmaps or not. - */ - public boolean mipmaps; - - /** - * Wrapping mode along U. - */ - public int wrapU; - - /** - * Wrapping mode along V. - */ - public int wrapV; - - /** - * Sets all the parameters to default values. - */ - public Parameters() { - this.target = TEX2D; - this.format = ARGB; - this.sampling = BILINEAR; - this.mipmaps = true; - this.wrapU = CLAMP; - this.wrapV = CLAMP; - } - - public Parameters(int format) { - this.target = TEX2D; - this.format = format; - this.sampling = BILINEAR; - this.mipmaps = true; - this.wrapU = CLAMP; - this.wrapV = CLAMP; - } - - public Parameters(int format, int sampling) { - this.target = TEX2D; - this.format = format; - this.sampling = sampling; - this.mipmaps = true; - this.wrapU = CLAMP; - this.wrapV = CLAMP; - } - - public Parameters(int format, int sampling, boolean mipmaps) { - this.target = TEX2D; - this.format = format; - this.mipmaps = mipmaps; - if (sampling == TRILINEAR && !mipmaps) { - this.sampling = BILINEAR; - } else { - this.sampling = sampling; - } - this.wrapU = CLAMP; - this.wrapV = CLAMP; - } - - public Parameters(int format, int sampling, boolean mipmaps, int wrap) { - this.target = TEX2D; - this.format = format; - this.mipmaps = mipmaps; - if (sampling == TRILINEAR && !mipmaps) { - this.sampling = BILINEAR; - } else { - this.sampling = sampling; - } - this.wrapU = wrap; - this.wrapV = wrap; - } - - public Parameters(Parameters src) { - set(src); - } - - public void set(int format) { - this.format = format; - } - - public void set(int format, int sampling) { - this.format = format; - this.sampling = sampling; - } - - public void set(int format, int sampling, boolean mipmaps) { - this.format = format; - this.sampling = sampling; - this.mipmaps = mipmaps; - } - - public void set(Parameters src) { - this.target = src.target; - this.format = src.format; - this.sampling = src.sampling; - this.mipmaps = src.mipmaps; - this.wrapU = src.wrapU; - this.wrapV = src.wrapV; - } - } - - /** - * This class stores a buffer copied from the buffer source. - * - */ - protected class BufferData { - int w, h; - // Native buffer object. - Object natBuf; - // Buffer viewed as int. - IntBuffer rgbBuf; - - BufferData(Object nat, IntBuffer rgb, int w, int h) { - natBuf = nat; - rgbBuf = rgb; - this.w = w; - this.h = h; - } - - void dispose() { - try { - // Disposing the native buffer. - disposeBufferMethod.invoke(bufferSource, new Object[] { natBuf }); - natBuf = null; - rgbBuf = null; - } catch (Exception e) { - e.printStackTrace(); - } - } - } -} +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2011-12 Ben Fry and Casey Reas + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.opengl; + +import processing.core.PApplet; +import processing.core.PConstants; +import processing.core.PGraphics; +import processing.core.PImage; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.LinkedList; +import java.util.NoSuchElementException; + +/** + * This class wraps an OpenGL texture. + * By Andres Colubri + * + */ +public class Texture implements PConstants { + // texture constants + + /** + * Texture with normalized UV. + */ + protected static final int TEX2D = 0; + /** + * Texture with un-normalized UV. + */ + protected static final int TEXRECT = 1; + + /** Point sampling: both magnification and minification filtering are set + * to nearest */ + protected static final int POINT = 2; + /** Linear sampling: magnification filtering is nearest, minification set + * to linear */ + protected static final int LINEAR = 3; + /** Bilinear sampling: both magnification filtering is set to linear and + * minification either to linear-mipmap-nearest (linear interplation is used + * within a mipmap, but not between different mipmaps). */ + protected static final int BILINEAR = 4; + /** Trilinear sampling: magnification filtering set to linear, minification to + * linear-mipmap-linear, which offers the best mipmap quality since linear + * interpolation to compute the value in each of two maps and then + * interpolates linearly between these two value. */ + protected static final int TRILINEAR = 5; + + public int width, height; + + public int glName; + public int glTarget; + public int glFormat; + public int glMinFilter; + public int glMagFilter; + public int glWrapS; + public int glWrapT; + public int glWidth; + public int glHeight; + + protected PApplet parent; // The Processing applet + protected PGraphicsOpenGL pg; // The main renderer + protected PGL pgl; // The interface between Processing and OpenGL. + protected PGL.Context context; // The context that created this texture. + protected PGraphicsOpenGL pgDraw; // The main renderer is the color buffer of. + + protected boolean usingMipmaps; + protected boolean usingRepeat; + protected float maxTexcoordU; + protected float maxTexcoordV; + protected boolean bound; + + protected boolean invertedX; + protected boolean invertedY; + + protected FrameBuffer tempFbo = null; + + /** Modified portion of the texture */ + protected boolean modified; + protected int mx1, my1, mx2, my2; + + protected Object bufferSource; + protected LinkedList bufferCache = null; + protected Method disposeBufferMethod; + public static final int MAX_BUFFER_CACHE_SIZE = 3; + + //////////////////////////////////////////////////////////// + + // Constructors. + + public Texture(PApplet parent) { + this.parent = parent; + + pg = (PGraphicsOpenGL)parent.g; + pgl = pg.pgl; + context = pgl.createEmptyContext(); + + pgDraw = null; + + glName = 0; + } + + + /** + * Creates an instance of PTexture with size width x height. The texture is + * initialized (empty) to that size. + * @param parent PApplet + * @param width int + * @param height int + */ + public Texture(PApplet parent, int width, int height) { + this(parent, width, height, new Parameters()); + } + + + /** + * Creates an instance of PTexture with size width x height and with the + * specified parameters. The texture is initialized (empty) to that size. + * @param parent PApplet + * @param width int + * @param height int + * @param params Parameters + */ + public Texture(PApplet parent, int width, int height, Object params) { + this.parent = parent; + + pg = (PGraphicsOpenGL)parent.g; + pgl = pg.pgl; + context = pgl.createEmptyContext(); + + pgDraw = null; + + glName = 0; + + init(width, height, (Parameters)params); + } + + + @Override + protected void finalize() throws Throwable { + try { + if (glName != 0) { + pg.finalizeTextureObject(glName, context.id()); + } + } finally { + super.finalize(); + } + } + + + //////////////////////////////////////////////////////////// + + // Init, resize methods + + + /** + * Sets the size of the image and texture to width x height. If the texture is + * already initialized, it first destroys the current OpenGL texture object + * and then creates a new one with the specified size. + * @param width int + * @param height int + */ + public void init(int width, int height) { + Parameters params; + if (0 < glName) { + // Re-initializing a pre-existing texture. + // We use the current parameters as default: + params = getParameters(); + } else { + // Just built-in default parameters otherwise: + params = new Parameters(); + } + init(width, height, params); + } + + + /** + * Sets the size of the image and texture to width x height, and the + * parameters of the texture to params. If the texture is already initialized, + * it first destroys the current OpenGL texture object and then creates a new + * one with the specified size. + * @param width int + * @param height int + * @param params GLTextureParameters + */ + public void init(int width, int height, Parameters params) { + setParameters(params); + setSize(width, height); + allocate(); + } + + + public void resize(int wide, int high) { + // Marking the texture object as finalized so it is deleted + // when creating the new texture. + release(); + + // Creating new texture with the appropriate size. + Texture tex = new Texture(parent, wide, high, getParameters()); + + // Copying the contents of this texture into tex. + tex.set(this); + + // Now, overwriting "this" with tex. + copyObject(tex); + + // Nullifying some utility objects so they are recreated with the + // appropriate size when needed. + tempFbo = null; + } + + + /** + * Returns true if the texture has been initialized. + * @return boolean + */ + public boolean available() { + return 0 < glName; + } + + /** + * Initializes the texture using GL parameters + */ + public void init(int glName, int glTarget, int glFormat, int glWidth, int glHeight, + int glMinFilter, int glMagFilter, int glWrapS, int glWrapT) { + this.glName = glName; + this.glTarget = glTarget; + this.glFormat = glFormat; + this.glWidth = glWidth; + this.glHeight = glHeight; + this.glMinFilter = glMinFilter; + this.glMagFilter = glMagFilter; + this.glWrapS = glWrapS; + this.glWrapT = glWrapT; + + width = glWidth; + height = glHeight; + maxTexcoordU = 1; + maxTexcoordV = 1; + + usingMipmaps = glMinFilter == PGL.LINEAR_MIPMAP_NEAREST || + glMinFilter == PGL.LINEAR_MIPMAP_LINEAR; + + usingRepeat = glWrapS == PGL.REPEAT || glWrapT == PGL.REPEAT; + } + + //////////////////////////////////////////////////////////// + + // Set methods + + + public void set(PImage img) { + Texture tex = (Texture)pg.getCache(img); + set(tex); + } + + + public void set(PImage img, int x, int y, int w, int h) { + Texture tex = (Texture)pg.getCache(img); + set(tex, x, y, w, h); + } + + + public void set(Texture tex) { + copyTexture(tex, 0, 0, tex.width, tex.height, true); + } + + + public void set(Texture tex, int x, int y, int w, int h) { + copyTexture(tex, x, y, w, h, true); + } + + + public void set(int texTarget, int texName, int texWidth, int texHeight, + int w, int h) { + copyTexture(texTarget, texName, texWidth, texHeight, 0, 0, w, h, true); + } + + + public void set(int texTarget, int texName, int texWidth, int texHeight, + int target, int tex, int x, int y, int w, int h) { + copyTexture(texTarget, texName, texWidth, texHeight, x, y, w, h, true); + } + + + public void set(int[] pixels) { + set(pixels, 0, 0, width, height, ARGB); + } + + + public void set(int[] pixels, int format) { + set(pixels, 0, 0, width, height, format); + } + + + public void set(int[] pixels, int x, int y, int w, int h) { + set(pixels, x, y, w, h, ARGB); + } + + + public void set(int[] pixels, int x, int y, int w, int h, int format) { + if (pixels == null) { + pixels = null; + PGraphics.showWarning("The pixels array is null."); + return; + } + if (pixels.length != w * h) { + PGraphics.showWarning("The pixels array has a length of " + + pixels.length + ", but it should be " + w * h); + return; + } + + if (pixels.length == 0) { + // Nothing to do (means that w == h == 0) but not an erroneous situation + return; + } + + boolean enabledTex = false; + if (!pgl.texturingIsEnabled(glTarget)) { + pgl.enableTexturing(glTarget); + enabledTex = true; + } + pgl.bindTexture(glTarget, glName); + + if (usingMipmaps) { + if (PGraphicsOpenGL.autoMipmapGenSupported) { + // Automatic mipmap generation. + int[] rgbaPixels = new int[w * h]; + convertToRGBA(pixels, rgbaPixels, format, w, h); + pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, + IntBuffer.wrap(rgbaPixels)); + pgl.generateMipmap(glTarget); + rgbaPixels = null; + } else { + // TODO: finish manual mipmap generation, replacing Bitmap with AWT's BufferedImage, + // making it work in npot textures (embed npot tex into larger pot tex?), subregions, + // and moving GLUtils.texImage2D (originally from Android SDK) into PGL. + // Actually, this whole code should go into PGL, so the Android implementation can + // use Bitmap, and desktop use BufferedImage. + + /* + if (w != width || h != height) { + System.err.println("Sorry but I don't know how to generate mipmaps for a subregion."); + return; + } + + // Code by Mike Miller obtained from here: + // http://insanitydesign.com/wp/2009/08/01/android-opengl-es-mipmaps/ + int w0 = glWidth; + int h0 = glHeight; + int[] argbPixels = new int[w0 * h0]; + convertToARGB(pixels, argbPixels, format); + int level = 0; + int denom = 1; + + // We create a Bitmap because then we use its built-in filtered downsampling + // functionality. + Bitmap bitmap = Bitmap.createBitmap(w0, h0, Config.ARGB_8888); + bitmap.setPixels(argbPixels, 0, w0, 0, 0, w0, h0); + + while (w0 >= 1 || h0 >= 1) { + //First of all, generate the texture from our bitmap and set it to the according level + GLUtils.texImage2D(glTarget, level, bitmap, 0); + + // We are done. + if (w0 == 1 && h0 == 1) { + break; + } + + // Increase the mipmap level + level++; + denom *= 2; + + // Downsampling bitmap. We must eventually arrive to the 1x1 level, + // and if the width and height are different, there will be a few 1D + // texture levels just before. + // This update formula also allows for NPOT resolutions. + w0 = PApplet.max(1, PApplet.floor((float)glWidth / denom)); + h0 = PApplet.max(1, PApplet.floor((float)glHeight / denom)); + // (see getScaledInstance in AWT Image) + Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, w0, h0, true); + + // Clean up + bitmap.recycle(); + bitmap = bitmap2; + } + */ + + int[] rgbaPixels = new int[w * h]; + convertToRGBA(pixels, rgbaPixels, format, w, h); + pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, + IntBuffer.wrap(rgbaPixels)); + rgbaPixels = null; + } + } else { + int[] rgbaPixels = new int[w * h]; + convertToRGBA(pixels, rgbaPixels, format, w, h); + pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, + IntBuffer.wrap(rgbaPixels)); + rgbaPixels = null; + } + + pgl.bindTexture(glTarget, 0); + if (enabledTex) { + pgl.disableTexturing(glTarget); + } + + updateTexels(x, y, w, h); + } + + + //////////////////////////////////////////////////////////// + + // Native set methods + + + public void setNative(int[] pixels) { + setNative(pixels, 0, 0, width, height); + } + + + public void setNative(int[] pixels, int x, int y, int w, int h) { + setNative(IntBuffer.wrap(pixels), x, y, w, h); + } + + + public void setNative(IntBuffer pixels, int x, int y, int w, int h) { + if (pixels == null) { + pixels = null; + PGraphics.showWarning("The pixel buffer is null."); + return; + } + if (pixels.capacity() != w * h) { + PGraphics.showWarning("The pixels array has a length of " + + pixels.capacity() + ", but it should be " + w * h); + return; + } + + if (pixels.capacity() == 0) { + // Nothing to do (means that w == h == 0) but not an erroneous situation + return; + } + + boolean enabledTex = false; + if (!pgl.texturingIsEnabled(glTarget)) { + pgl.enableTexturing(glTarget); + enabledTex = true; + } + pgl.bindTexture(glTarget, glName); + + if (usingMipmaps) { + if (PGraphicsOpenGL.autoMipmapGenSupported) { + pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, + pixels); + pgl.generateMipmap(glTarget); + } else { + pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, + pixels); + } + } else { + pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, + pixels); + } + + pgl.bindTexture(glTarget, 0); + if (enabledTex) { + pgl.disableTexturing(glTarget); + } + + updateTexels(x, y, w, h); + } + + + //////////////////////////////////////////////////////////// + + // Get methods + + + /** + * Copy texture to pixels. Involves video memory to main memory transfer (slow). + */ + public void get(int[] pixels) { + if (pixels == null) { + throw new RuntimeException("Trying to copy texture to null pixels array"); + } + if (pixels.length != width * height) { + throw new RuntimeException("Trying to copy texture to pixels array of " + + "wrong size"); + } + + if (tempFbo == null) { + tempFbo = new FrameBuffer(parent, glWidth, glHeight); + } + + // Attaching the texture to the color buffer of a FBO, binding the FBO and + // reading the pixels from the current draw buffer (which is the color + // buffer of the FBO). + tempFbo.setColorBuffer(this); + pg.pushFramebuffer(); + pg.setFramebuffer(tempFbo); + tempFbo.readPixels(); + pg.popFramebuffer(); + + tempFbo.getPixels(pixels); + convertToARGB(pixels); + + if (invertedX) flipArrayOnX(pixels, 1); + if (invertedY) flipArrayOnY(pixels, 1); + } + + + /** + * Copies the contents of the texture to the pixels array. + * @param pixels + */ + public void loadPixels(int[] pixels) { + if (hasBuffers()) { + // Updates the texture AND the pixels array of the image at the same time, + // getting the pixels directly from the buffer data (and thus avoiding + // expensive transfer between video and main memory). + bufferUpdate(pixels); + } + + if (isModified()) { + // Regular pixel copy from texture. + get(pixels); + } + + setModified(false); + } + + + //////////////////////////////////////////////////////////// + + // Put methods (the source texture is not resized to cover the entire + // destination). + + + public void put(Texture tex) { + copyTexture(tex, 0, 0, tex.width, tex.height, false); + } + + + public void put(Texture tex, int x, int y, int w, int h) { + copyTexture(tex, x, y, w, h, false); + } + + + public void put(int texTarget, int texName, int texWidth, int texHeight, + int w, int h) { + copyTexture(texTarget, texName, texWidth, texHeight, 0, 0, w, h, false); + } + + + public void put(int texTarget, int texName, int texWidth, int texHeight, + int target, int tex, int x, int y, int w, int h) { + copyTexture(texTarget, texName, texWidth, texHeight, x, y, w, h, false); + } + + + //////////////////////////////////////////////////////////// + + // Get OpenGL parameters + + + /** + * Returns true or false whether or not the texture is using mipmaps. + * @return boolean + */ + public boolean usingMipmaps() { + return usingMipmaps; + } + + + public void usingMipmaps(boolean mipmaps, int sampling) { + if (mipmaps) { + if (glMinFilter != PGL.LINEAR_MIPMAP_NEAREST && + glMinFilter != PGL.LINEAR_MIPMAP_LINEAR) { + if (sampling == POINT) { + glMagFilter = PGL.NEAREST; + glMinFilter = PGL.NEAREST; + } else if (sampling == LINEAR) { + glMagFilter = PGL.NEAREST; + glMinFilter = + PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; + } else if (sampling == BILINEAR) { + glMagFilter = PGL.LINEAR; + glMinFilter = + PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; + } else if (sampling == TRILINEAR) { + glMagFilter = PGL.LINEAR; + glMinFilter = + PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_LINEAR : PGL.LINEAR; + } else { + throw new RuntimeException("Unknown texture filtering mode"); + } + } + + usingMipmaps = true; + } else { + if (glMinFilter == PGL.LINEAR_MIPMAP_NEAREST || + glMinFilter == PGL.LINEAR_MIPMAP_LINEAR) { + glMinFilter = PGL.LINEAR; + } + usingMipmaps = false; + } + + bind(); + pgl.texParameteri(glTarget, PGL.TEXTURE_MIN_FILTER, glMinFilter); + pgl.texParameteri(glTarget, PGL.TEXTURE_MAG_FILTER, glMagFilter); + if (usingMipmaps) { + if (PGraphicsOpenGL.autoMipmapGenSupported) { + pgl.generateMipmap(glTarget); + } else { + // TODO: need manual generation here.. + } + } + unbind(); + } + + + /** + * Returns true or false whether or not the texture is using repeat wrap mode + * along either U or V directions. + * @return boolean + */ + public boolean usingRepeat() { + return usingRepeat; + } + + + public void usingRepeat(boolean repeat) { + if (repeat) { + glWrapS = PGL.REPEAT; + glWrapT = PGL.REPEAT; + usingRepeat = true; + } else { + glWrapS = PGL.CLAMP_TO_EDGE; + glWrapT = PGL.CLAMP_TO_EDGE; + usingRepeat = false; + } + + bind(); + pgl.texParameteri(glTarget, PGL.TEXTURE_WRAP_S, glWrapS); + pgl.texParameteri(glTarget, PGL.TEXTURE_WRAP_T, glWrapT); + unbind(); + } + + + /** + * Returns the maximum possible value for the texture coordinate U + * (horizontal). + * @return float + */ + public float maxTexcoordU() { + return maxTexcoordU; + } + + + /** + * Returns the maximum possible value for the texture coordinate V (vertical). + * @return float + */ + public float maxTexcoordV() { + return maxTexcoordV; + } + + + /** + * Returns true if the texture is inverted along the horizontal direction. + * @return boolean; + */ + public boolean invertedX() { + return invertedX; + } + + + /** + * Sets the texture as inverted or not along the horizontal direction. + * @param v boolean; + */ + public void invertedX(boolean v) { + invertedX = v; + } + + + /** + * Returns true if the texture is inverted along the vertical direction. + * @return boolean; + */ + public boolean invertedY() { + return invertedY; + } + + + /** + * Sets the texture as inverted or not along the vertical direction. + * @param v boolean; + */ + public void invertedY(boolean v) { + invertedY = v; + } + + + //////////////////////////////////////////////////////////// + + // Bind/unbind + + + public void bind() { + // Binding a texture automatically enables texturing for the + // texture target from that moment onwards. Unbinding the texture + // won't disable texturing. + if (!pgl.texturingIsEnabled(glTarget)) { + pgl.enableTexturing(glTarget); + } + pgl.bindTexture(glTarget, glName); + bound = true; + } + + + public void unbind() { + if (pgl.textureIsBound(glTarget, glName)) { + // We don't want to unbind another texture + // that might be bound instead of this one. + if (!pgl.texturingIsEnabled(glTarget)) { + pgl.enableTexturing(glTarget); + pgl.bindTexture(glTarget, 0); + pgl.disableTexturing(glTarget); + } else { + pgl.bindTexture(glTarget, 0); + } + } + bound = false; + } + + + public boolean bound() { + // A true result might not necessarily mean that texturing is enabled + // (a texture can be bound to the target, but texturing is disabled). + return bound; + } + + + ////////////////////////////////////////////////////////////// + + // Modified flag + + + public boolean isModified() { + return modified; + } + + + public void setModified() { + modified = true; + } + + + public void setModified(boolean m) { + modified = m; + } + + + public int getModifiedX1() { + return mx1; + } + + + public int getModifiedX2() { + return mx2; + } + + + public int getModifiedY1() { + return my1; + } + + + public int getModifiedY2() { + return my2; + } + + + public void updateTexels() { + updateTexelsImpl(0, 0, width, height); + } + + + public void updateTexels(int x, int y, int w, int h) { + updateTexelsImpl(x, y, w, h); + } + + + protected void updateTexelsImpl(int x, int y, int w, int h) { + int x2 = x + w; + int y2 = y + h; + + if (!modified) { + mx1 = PApplet.max(0, x); + mx2 = PApplet.min(width - 1, x2); + my1 = PApplet.max(0, y); + my2 = PApplet.min(height - 1, y2); + modified = true; + + } else { + if (x < mx1) mx1 = PApplet.max(0, x); + if (x > mx2) mx2 = PApplet.min(width - 1, x); + if (y < my1) my1 = PApplet.max(0, y); + if (y > my2) my2 = y; + + if (x2 < mx1) mx1 = PApplet.max(0, x2); + if (x2 > mx2) mx2 = PApplet.min(width - 1, x2); + if (y2 < my1) my1 = PApplet.max(0, y2); + if (y2 > my2) my2 = PApplet.min(height - 1, y2); + } + } + + + //////////////////////////////////////////////////////////// + + // Buffer sink interface. + + + public void setBufferSource(Object source) { + bufferSource = source; + getSourceMethods(); + } + + + public void copyBufferFromSource(Object natRef, ByteBuffer byteBuf, + int w, int h) { + if (bufferCache == null) { + bufferCache = new LinkedList(); + } + + if (bufferCache.size() + 1 <= MAX_BUFFER_CACHE_SIZE) { + bufferCache.add(new BufferData(natRef, byteBuf.asIntBuffer(), w, h)); + } else { + // The buffer cache reached the maximum size, so we just dispose + // the new buffer. + try { + disposeBufferMethod.invoke(bufferSource, new Object[] { natRef }); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + + public boolean hasBufferSource() { + return bufferSource != null; + } + + + public boolean hasBuffers() { + return bufferSource != null && bufferCache != null && + 0 < bufferCache.size(); + } + + + protected boolean bufferUpdate() { + BufferData data = null; + try { + data = bufferCache.remove(0); + } catch (NoSuchElementException ex) { + PGraphics.showWarning("Don't have pixel data to copy to texture"); + } + + if (data != null) { + if ((data.w != width) || (data.h != height)) { + init(data.w, data.h); + } + setNative(data.rgbBuf, 0, 0, width, height); + + data.dispose(); + + return true; + } else { + return false; + } + } + + + protected boolean bufferUpdate(int[] pixels) { + BufferData data = null; + try { + data = bufferCache.remove(0); + } catch (NoSuchElementException ex) { + PGraphics.showWarning("Don't have pixel data to copy to texture"); + } + + if (data != null) { + if ((data.w != width) || (data.h != height)) { + init(data.w, data.h); + } + setNative(data.rgbBuf, 0, 0, width, height); + + data.rgbBuf.get(pixels); + convertToARGB(pixels); + + data.dispose(); + + return true; + } else { + return false; + } + } + + + protected void getSourceMethods() { + try { + disposeBufferMethod = bufferSource.getClass(). + getMethod("disposeBuffer", new Class[] { Object.class }); + } catch (Exception e) { + throw new RuntimeException("Provided source object doesn't have a " + + "disposeBuffer method."); + } + } + + + //////////////////////////////////////////////////////////// + + // Utilities + + + /** + * Flips intArray along the X axis. + * @param intArray int[] + * @param mult int + */ + protected void flipArrayOnX(int[] intArray, int mult) { + int index = 0; + int xindex = mult * (width - 1); + for (int x = 0; x < width / 2; x++) { + for (int y = 0; y < height; y++) { + int i = index + mult * y * width; + int j = xindex + mult * y * width; + + for (int c = 0; c < mult; c++) { + int temp = intArray[i]; + intArray[i] = intArray[j]; + intArray[j] = temp; + + i++; + j++; + } + + } + index += mult; + xindex -= mult; + } + } + + + /** + * Flips intArray along the Y axis. + * @param intArray int[] + * @param mult int + */ + protected void flipArrayOnY(int[] intArray, int mult) { + int index = 0; + int yindex = mult * (height - 1) * width; + for (int y = 0; y < height / 2; y++) { + for (int x = 0; x < mult * width; x++) { + int temp = intArray[index]; + intArray[index] = intArray[yindex]; + intArray[yindex] = temp; + + index++; + yindex++; + } + yindex -= mult * width * 2; + } + } + + + /** + * Reorders a pixel array in the given format into the order required by + * OpenGL (RGBA). Both arrays are assumed to be of the same length. The width + * and height parameters are used in the YUV420 to RBGBA conversion. + * @param intArray int[] + * @param tIntArray int[] + * @param arrayFormat int + * @param w int + * @param h int + */ + protected void convertToRGBA(int[] intArray, int[] tIntArray, int arrayFormat, + int w, int h) { + if (PGL.BIG_ENDIAN) { + switch (arrayFormat) { + case ALPHA: + + // Converting from xxxA into RGBA. RGB is set to white + // (0xFFFFFF, i.e.: (255, 255, 255)) + for (int i = 0; i< intArray.length; i++) { + tIntArray[i] = 0xFFFFFF00 | intArray[i]; + } + break; + + case RGB: + + // Converting xRGB into RGBA. A is set to 0xFF (255, full opacity). + for (int i = 0; i< intArray.length; i++) { + int pixel = intArray[i]; + tIntArray[i] = (pixel << 8) | 0xFF; + } + break; + + case ARGB: + + // Converting ARGB into RGBA. Shifting RGB to 8 bits to the left, + // and bringing A to the first byte. + for (int i = 0; i< intArray.length; i++) { + int pixel = intArray[i]; + tIntArray[i] = (pixel << 8) | ((pixel >> 24) & 0xFF); + } + break; + } + + } else { + // LITTLE_ENDIAN + // ARGB native, and RGBA opengl means ABGR on windows + // for the most part just need to swap two components here + // the sun.cpu.endian here might be "false", oddly enough.. + // (that's why just using an "else", rather than check for "little") + + switch (arrayFormat) { + case ALPHA: + + // Converting xxxA into ARGB, with RGB set to white. + for (int i = 0; i< intArray.length; i++) { + tIntArray[i] = (intArray[i] << 24) | 0x00FFFFFF; + } + break; + + case RGB: + + // We need to convert xRGB into ABGR, + // so R and B must be swapped, and the x just made 0xFF. + for (int i = 0; i< intArray.length; i++) { + int pixel = intArray[i]; + tIntArray[i] = 0xFF000000 | + ((pixel & 0xFF) << 16) | + ((pixel & 0xFF0000) >> 16) | + (pixel & 0x0000FF00); + } + break; + + case ARGB: + + // We need to convert ARGB into ABGR, + // so R and B must be swapped, A and G just brought back in. + for (int i = 0; i < intArray.length; i++) { + int pixel = intArray[i]; + tIntArray[i] = ((pixel & 0xFF) << 16) | + ((pixel & 0xFF0000) >> 16) | + (pixel & 0xFF00FF00); + } + break; + + } + + } + } + + + /** + * Reorders an OpenGL pixel array (RGBA) into ARGB. The array must be + * of size width * height. + * @param intArray int[] + */ + protected void convertToARGB(int[] intArray) { + int t = 0; + int p = 0; + if (PGL.BIG_ENDIAN) { + + // RGBA to ARGB conversion: shifting RGB 8 bits to the right, + // and placing A 24 bits to the left. + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pixel = intArray[p++]; + intArray[t++] = (pixel >> 8) | ((pixel << 24) & 0xFF000000); + } + } + + } else { + + // We have to convert ABGR into ARGB, so R and B must be swapped, + // A and G just brought back in. + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pixel = intArray[p++]; + intArray[t++] = ((pixel & 0xFF) << 16) | + ((pixel & 0xFF0000) >> 16) | + (pixel & 0xFF00FF00); + + } + } + } + } + + + + /////////////////////////////////////////////////////////// + + // Allocate/release texture. + + + protected void setSize(int w, int h) { + width = w; + height = h; + + if (PGraphicsOpenGL.npotTexSupported) { + glWidth = w; + glHeight = h; + } else { + glWidth = PGL.nextPowerOfTwo(w); + glHeight = PGL.nextPowerOfTwo(h); + } + + if (glWidth > PGraphicsOpenGL.maxTextureSize || + glHeight > PGraphicsOpenGL.maxTextureSize) { + glWidth = glHeight = 0; + throw new RuntimeException("Image width and height cannot be" + + " larger than " + + PGraphicsOpenGL.maxTextureSize + + " with this graphics card."); + } + + // If non-power-of-two textures are not supported, and the specified width + // or height is non-power-of-two, then glWidth (glHeight) will be greater + // than w (h) because it is chosen to be the next power of two, and this + // quotient will give the appropriate maximum texture coordinate value given + // this situation. + maxTexcoordU = (float)width / glWidth; + maxTexcoordV = (float)height / glHeight; + } + + + /** + * Allocates the opengl texture object. + */ + protected void allocate() { + release(); // Just in the case this object is being re-allocated. + + boolean enabledTex = false; + if (!pgl.texturingIsEnabled(glTarget)) { + pgl.enableTexturing(glTarget); + enabledTex = true; + } + + context = pgl.getCurrentContext(); + glName = pg.createTextureObject(context.id()); + + pgl.bindTexture(glTarget, glName); + pgl.texParameteri(glTarget, PGL.TEXTURE_MIN_FILTER, glMinFilter); + pgl.texParameteri(glTarget, PGL.TEXTURE_MAG_FILTER, glMagFilter); + pgl.texParameteri(glTarget, PGL.TEXTURE_WRAP_S, glWrapS); + pgl.texParameteri(glTarget, PGL.TEXTURE_WRAP_T, glWrapT); + if (PGraphicsOpenGL.anisoSamplingSupported) { + pgl.texParameterf(glTarget, PGL.TEXTURE_MAX_ANISOTROPY, + PGraphicsOpenGL.maxAnisoAmount); + } + + // First, we use glTexImage2D to set the full size of the texture (glW/glH + // might be diff from w/h in the case that the GPU doesn't support NPOT + // textures) + pgl.texImage2D(glTarget, 0, glFormat, glWidth, glHeight, 0, + PGL.RGBA, PGL.UNSIGNED_BYTE, null); + + // Makes sure that the texture buffer in video memory doesn't contain + // any garbage. + pgl.initTexture(glTarget, PGL.RGBA, width, height); + + pgl.bindTexture(glTarget, 0); + if (enabledTex) { + pgl.disableTexturing(glTarget); + } + bound = false; + } + + + /** + * Marks the texture object for deletion. + */ + protected void release() { + if (glName != 0) { + pg.finalizeTextureObject(glName, context.id()); + glName = 0; + } + } + + + protected boolean contextIsOutdated() { + boolean outdated = !pgl.contextIsCurrent(context); + if (outdated) { + // Removing the texture object from the renderer's list so it + // doesn't get deleted by OpenGL. The texture object was + // automatically disposed when the old context was destroyed. + pg.removeTextureObject(glName, context.id()); + + // And then set the id to zero, so it doesn't try to be + // deleted when the object's finalizer is invoked by the GC. + glName = 0; + } + return outdated; + } + + + protected void colorBufferOf(PGraphicsOpenGL pgDraw) { + this.pgDraw = pgDraw; + } + + + protected boolean isColorBuffer() { + return pgDraw != null; + } + + + /////////////////////////////////////////////////////////// + + // Utilities. + + + // Copies source texture tex into this. + protected void copyTexture(Texture tex, int x, int y, int w, int h, + boolean scale) { + if (tex == null) { + throw new RuntimeException("Source texture is null"); + } + + if (tempFbo == null) { + tempFbo = new FrameBuffer(parent, glWidth, glHeight); + } + + // This texture is the color (destination) buffer of the FBO. + tempFbo.setColorBuffer(this); + tempFbo.disableDepthTest(); + + // FBO copy: + pg.pushFramebuffer(); + pg.setFramebuffer(tempFbo); + if (scale) { + // Rendering tex into "this", and scaling the source rectangle + // to cover the entire destination region. + pgl.drawTexture(tex.glTarget, tex.glName, tex.glWidth, tex.glHeight, + x, y, w, h, 0, 0, width, height); + + } else { + // Rendering tex into "this" but without scaling so the contents + // of the source texture fall in the corresponding texels of the + // destination. + pgl.drawTexture(tex.glTarget, tex.glName, tex.glWidth, tex.glHeight, + x, y, w, h, x, y, w, h); + } + pg.popFramebuffer(); + updateTexels(x, y, w, h); + } + + + // Copies source texture tex into this. + protected void copyTexture(int texTarget, int texName, + int texWidth, int texHeight, + int x, int y, int w, int h, boolean scale) { + if (tempFbo == null) { + tempFbo = new FrameBuffer(parent, glWidth, glHeight); + } + + // This texture is the color (destination) buffer of the FBO. + tempFbo.setColorBuffer(this); + tempFbo.disableDepthTest(); + + // FBO copy: + pg.pushFramebuffer(); + pg.setFramebuffer(tempFbo); + if (scale) { + // Rendering tex into "this", and scaling the source rectangle + // to cover the entire destination region. + pgl.drawTexture(texTarget, texName, texWidth, texHeight, + x, y, w, h, 0, 0, width, height); + + } else { + // Rendering tex into "this" but without scaling so the contents + // of the source texture fall in the corresponding texels of the + // destination. + pgl.drawTexture(texTarget, texName, texWidth, texHeight, + x, y, w, h, x, y, w, h); + } + pg.popFramebuffer(); + updateTexels(x, y, w, h); + } + + + protected void copyObject(Texture src) { + // The OpenGL texture of this object is replaced with the one from the + // source object, so we delete the former to avoid resource wasting. + release(); + + width = src.width; + height = src.height; + + parent = src.parent; + pg = src.pg; + + glName = src.glName; + glTarget = src.glTarget; + glFormat = src.glFormat; + glMinFilter = src.glMinFilter; + glMagFilter = src.glMagFilter; + + glWidth= src.glWidth; + glHeight = src.glHeight; + + usingMipmaps = src.usingMipmaps; + usingRepeat = src.usingRepeat; + maxTexcoordU = src.maxTexcoordU; + maxTexcoordV = src.maxTexcoordV; + + invertedX = src.invertedX; + invertedY = src.invertedY; + } + + + /////////////////////////////////////////////////////////// + + // Parameter handling + + + public Parameters getParameters() { + Parameters res = new Parameters(); + + if (glTarget == PGL.TEXTURE_2D) { + res.target = TEX2D; + } + + if (glFormat == PGL.RGB) { + res.format = RGB; + } else if (glFormat == PGL.RGBA) { + res.format = ARGB; + } else if (glFormat == PGL.ALPHA) { + res.format = ALPHA; + } + + if (glMagFilter == PGL.NEAREST && glMinFilter == PGL.NEAREST) { + res.sampling = POINT; + res.mipmaps = false; + } else if (glMagFilter == PGL.NEAREST && glMinFilter == PGL.LINEAR) { + res.sampling = LINEAR; + res.mipmaps = false; + } else if (glMagFilter == PGL.NEAREST && + glMinFilter == PGL.LINEAR_MIPMAP_NEAREST) { + res.sampling = LINEAR; + res.mipmaps = true; + } else if (glMagFilter == PGL.LINEAR && glMinFilter == PGL.LINEAR) { + res.sampling = BILINEAR; + res.mipmaps = false; + } else if (glMagFilter == PGL.LINEAR && + glMinFilter == PGL.LINEAR_MIPMAP_NEAREST) { + res.sampling = BILINEAR; + res.mipmaps = true; + } else if (glMagFilter == PGL.LINEAR && + glMinFilter == PGL.LINEAR_MIPMAP_LINEAR) { + res.sampling = TRILINEAR; + res.mipmaps = true; + } + + if (glWrapS == PGL.CLAMP_TO_EDGE) { + res.wrapU = CLAMP; + } else if (glWrapS == PGL.REPEAT) { + res.wrapU = REPEAT; + } + + if (glWrapT == PGL.CLAMP_TO_EDGE) { + res.wrapV = CLAMP; + } else if (glWrapT == PGL.REPEAT) { + res.wrapV = REPEAT; + } + + return res; + } + + + /** + * Sets texture target and internal format according to the target and + * type specified. + * @param target int + * @param params GLTextureParameters + */ + protected void setParameters(Parameters params) { + if (params.target == TEX2D) { + glTarget = PGL.TEXTURE_2D; + } else { + throw new RuntimeException("Unknown texture target"); + } + + if (params.format == RGB) { + glFormat = PGL.RGB; + } else if (params.format == ARGB) { + glFormat = PGL.RGBA; + } else if (params.format == ALPHA) { + glFormat = PGL.ALPHA; + } else { + throw new RuntimeException("Unknown texture format"); + } + + if (params.sampling == POINT) { + glMagFilter = PGL.NEAREST; + glMinFilter = PGL.NEAREST; + } else if (params.sampling == LINEAR) { + glMagFilter = PGL.NEAREST; + glMinFilter = params.mipmaps && PGL.MIPMAPS_ENABLED ? + PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; + } else if (params.sampling == BILINEAR) { + glMagFilter = PGL.LINEAR; + glMinFilter = params.mipmaps && PGL.MIPMAPS_ENABLED ? + PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; + } else if (params.sampling == TRILINEAR) { + glMagFilter = PGL.LINEAR; + glMinFilter = params.mipmaps && PGL.MIPMAPS_ENABLED ? + PGL.LINEAR_MIPMAP_LINEAR : PGL.LINEAR; + } else { + throw new RuntimeException("Unknown texture filtering mode"); + } + + if (params.wrapU == CLAMP) { + glWrapS = PGL.CLAMP_TO_EDGE; + } else if (params.wrapU == REPEAT) { + glWrapS = PGL.REPEAT; + } else { + throw new RuntimeException("Unknown wrapping mode"); + } + + if (params.wrapV == CLAMP) { + glWrapT = PGL.CLAMP_TO_EDGE; + } else if (params.wrapV == REPEAT) { + glWrapT = PGL.REPEAT; + } else { + throw new RuntimeException("Unknown wrapping mode"); + } + + usingMipmaps = glMinFilter == PGL.LINEAR_MIPMAP_NEAREST || + glMinFilter == PGL.LINEAR_MIPMAP_LINEAR; + + usingRepeat = glWrapS == PGL.REPEAT || glWrapT == PGL.REPEAT; + + invertedX = false; + invertedY = false; + } + + + /////////////////////////////////////////////////////////////////////////// + + // Parameters object + + + /** + * This class stores the parameters for a texture: target, internal format, + * minimization filter and magnification filter. + */ + static public class Parameters { + /** + * Texture target. + */ + public int target; + + /** + * Texture internal format. + */ + public int format; + + /** + * Texture filtering (POINT, LINEAR, BILINEAR or TRILINEAR). + */ + public int sampling; + + /** + * Use mipmaps or not. + */ + public boolean mipmaps; + + /** + * Wrapping mode along U. + */ + public int wrapU; + + /** + * Wrapping mode along V. + */ + public int wrapV; + + /** + * Sets all the parameters to default values. + */ + public Parameters() { + this.target = TEX2D; + this.format = ARGB; + this.sampling = BILINEAR; + this.mipmaps = true; + this.wrapU = CLAMP; + this.wrapV = CLAMP; + } + + public Parameters(int format) { + this.target = TEX2D; + this.format = format; + this.sampling = BILINEAR; + this.mipmaps = true; + this.wrapU = CLAMP; + this.wrapV = CLAMP; + } + + public Parameters(int format, int sampling) { + this.target = TEX2D; + this.format = format; + this.sampling = sampling; + this.mipmaps = true; + this.wrapU = CLAMP; + this.wrapV = CLAMP; + } + + public Parameters(int format, int sampling, boolean mipmaps) { + this.target = TEX2D; + this.format = format; + this.mipmaps = mipmaps; + if (sampling == TRILINEAR && !mipmaps) { + this.sampling = BILINEAR; + } else { + this.sampling = sampling; + } + this.wrapU = CLAMP; + this.wrapV = CLAMP; + } + + public Parameters(int format, int sampling, boolean mipmaps, int wrap) { + this.target = TEX2D; + this.format = format; + this.mipmaps = mipmaps; + if (sampling == TRILINEAR && !mipmaps) { + this.sampling = BILINEAR; + } else { + this.sampling = sampling; + } + this.wrapU = wrap; + this.wrapV = wrap; + } + + public Parameters(Parameters src) { + set(src); + } + + public void set(int format) { + this.format = format; + } + + public void set(int format, int sampling) { + this.format = format; + this.sampling = sampling; + } + + public void set(int format, int sampling, boolean mipmaps) { + this.format = format; + this.sampling = sampling; + this.mipmaps = mipmaps; + } + + public void set(Parameters src) { + this.target = src.target; + this.format = src.format; + this.sampling = src.sampling; + this.mipmaps = src.mipmaps; + this.wrapU = src.wrapU; + this.wrapV = src.wrapV; + } + } + + /** + * This class stores a buffer copied from the buffer source. + * + */ + protected class BufferData { + int w, h; + // Native buffer object. + Object natBuf; + // Buffer viewed as int. + IntBuffer rgbBuf; + + BufferData(Object nat, IntBuffer rgb, int w, int h) { + natBuf = nat; + rgbBuf = rgb; + this.w = w; + this.h = h; + } + + void dispose() { + try { + // Disposing the native buffer. + disposeBufferMethod.invoke(bufferSource, new Object[] { natBuf }); + natBuf = null; + rgbBuf = null; + } catch (Exception e) { + e.printStackTrace(); + } + } + } +}