From f1338a4472ed103e03e5090fba4a710454b5a19f Mon Sep 17 00:00:00 2001 From: codeanticode Date: Thu, 22 Mar 2012 17:30:36 +0000 Subject: [PATCH] FBO-based onscreen rendering for incremental drawing on Android is working again, added some more PGL utilities. --- .../src/processing/core/PFramebuffer.java | 276 +++------- android/core/src/processing/core/PGL.java | 490 +++++++++++++----- .../processing/core/PGraphicsAndroid3D.java | 34 +- android/core/src/processing/core/PShader.java | 126 +++-- .../core/src/processing/core/PTexture.java | 37 +- .../processing/opengl/FillShaderFragTex.glsl | 2 + .../src/processing/opengl/PFramebuffer.java | 34 +- .../opengl/src/processing/opengl/PGL.java | 214 +++++++- .../processing/opengl/PGraphicsOpenGL.java | 18 +- 9 files changed, 770 insertions(+), 461 deletions(-) diff --git a/android/core/src/processing/core/PFramebuffer.java b/android/core/src/processing/core/PFramebuffer.java index 43ab3b33c..1fe02e77b 100644 --- a/android/core/src/processing/core/PFramebuffer.java +++ b/android/core/src/processing/core/PFramebuffer.java @@ -58,10 +58,8 @@ public class PFramebuffer implements PConstants { protected PTexture[] colorBufferTex; protected boolean screenFb; - protected boolean noDepth; - protected boolean fboMode; + protected boolean noDepth; - protected PTexture backupTexture; protected IntBuffer pixelBuffer; PFramebuffer(PApplet parent, int w, int h) { @@ -84,8 +82,6 @@ public class PFramebuffer implements PConstants { glStencilBufferID = 0; glDepthStencilBufferID = 0; glColorBufferMultisampleID = 0; - - fboMode = PGraphicsAndroid3D.fboSupported; if (screen) { // If this framebuffer is used to represent a on-screen buffer, @@ -135,14 +131,6 @@ public class PFramebuffer implements PConstants { noDepth = false; pixelBuffer = null; - - if (!screenFb && !fboMode) { - // When FBOs are not available, rendering to texture is implemented by saving a portion of - // the screen, doing the "offscreen" rendering on this portion, copying the screen color - // buffer to the texture bound as color buffer to this PFramebuffer object and then drawing - // the backup texture back on the screen. - backupTexture = new PTexture(parent, width, height, new PTexture.Parameters(ARGB, POINT)); - } } @@ -185,27 +173,10 @@ public class PFramebuffer implements PConstants { } public void bind() { - // if context is outdated ?? - - if (screenFb) { - if (PGraphicsAndroid3D.fboSupported) { pgl.glBindFramebuffer(PGL.GL_FRAMEBUFFER, 0); - } - } else if (fboMode) { - pgl.glBindFramebuffer(PGL.GL_FRAMEBUFFER, glFboID); } else { - backupScreen(); - - if (0 < numColorBuffers) { - // Drawing the current contents of the first color buffer to emulate - // front-back buffer swap. - pg.drawTexture(colorBufferTex[0].glTarget, colorBufferTex[0].glID, width, height, 0, 0, width, height, 0, 0, width, height); - } - - if (noDepth) { - pgl.glDisable(PGL.GL_DEPTH_TEST); - } + pgl.glBindFramebuffer(PGL.GL_FRAMEBUFFER, glFboID); } } @@ -222,46 +193,7 @@ public class PFramebuffer implements PConstants { pgl.glEnable(PGL.GL_DEPTH_TEST); } } - - if (!screenFb && !fboMode) { - copyToColorBuffers(); - restoreBackup(); - if (!noDepth) { - // Reading the contents of the depth buffer is not possible in OpenGL ES: - // http://www.idevgames.com/forum/archive/index.php?t-15828.html - // so if this framebuffer uses depth and is offscreen with no FBOs, then - // the depth buffer is cleared to avoid artifacts when rendering more stuff - // after this offscreen render. - // A consequence of this behavior is that all the offscreen rendering when - // no FBOs are available should be done before any onscreen drawing. - pgl.glClearColor(0, 0, 0, 0); - pgl.glClear(PGL.GL_DEPTH_BUFFER_BIT); - } - } } - - // Saves content of the screen into the backup texture. - public void backupScreen() { - if (pixelBuffer == null) createPixelBuffer(); - pixelBuffer.rewind(); - pgl.glReadPixels(0, 0, width, height, PGL.GL_RGBA, PGL.GL_UNSIGNED_BYTE, pixelBuffer); - copyToTexture(pixelBuffer, backupTexture.glID, backupTexture.glTarget); - } - - // Draws the contents of the backup texture to the screen. - public void restoreBackup() { - pg.drawTexture(backupTexture, 0, 0, width, height, 0, 0, width, height); - } - - // Copies current content of screen to color buffers. - public void copyToColorBuffers() { - if (pixelBuffer == null) createPixelBuffer(); - pixelBuffer.rewind(); - pgl.glReadPixels(0, 0, width, height, PGL.GL_RGBA, PGL.GL_UNSIGNED_BYTE, pixelBuffer); - for (int i = 0; i < numColorBuffers; i++) { - copyToTexture(pixelBuffer, colorBufferTex[i].glID, colorBufferTex[i].glTarget); - } - } public void readPixels() { if (pixelBuffer == null) createPixelBuffer(); @@ -314,23 +246,21 @@ public class PFramebuffer implements PConstants { colorBufferTex[i] = textures[i]; } - if (fboMode) { - pg.pushFramebuffer(); - pg.setFramebuffer(this); + pg.pushFramebuffer(); + pg.setFramebuffer(this); - // Making sure nothing is attached. - for (int i = 0; i < numColorBuffers; i++) { - pgl.glFramebufferTexture2D(PGL.GL_FRAMEBUFFER, PGL.GL_COLOR_ATTACHMENT0 + i, PGL.GL_TEXTURE_2D, 0, 0); - } - - for (int i = 0; i < numColorBuffers; i++) { - pgl.glFramebufferTexture2D(PGL.GL_FRAMEBUFFER, PGL.GL_COLOR_ATTACHMENT0 + i, colorBufferTex[i].glTarget, colorBufferTex[i].glID, 0); - } - - validateFbo(); - - pg.popFramebuffer(); + // Making sure nothing is attached. + for (int i = 0; i < numColorBuffers; i++) { + pgl.glFramebufferTexture2D(PGL.GL_FRAMEBUFFER, PGL.GL_COLOR_ATTACHMENT0 + i, PGL.GL_TEXTURE_2D, 0, 0); } + + for (int i = 0; i < numColorBuffers; i++) { + pgl.glFramebufferTexture2D(PGL.GL_FRAMEBUFFER, PGL.GL_COLOR_ATTACHMENT0 + i, colorBufferTex[i].glTarget, colorBufferTex[i].glID, 0); + } + + pgl.validateFramebuffer(); + + pg.popFramebuffer(); } @@ -346,10 +276,8 @@ public class PFramebuffer implements PConstants { if (screenFb) { glFboID = 0; - } else if (fboMode) { + } else { glFboID = pg.createFrameBufferObject(); - } else { - glFboID = 0; } // create the rest of the stuff... @@ -406,8 +334,6 @@ public class PFramebuffer implements PConstants { for (int i = 0; i < numColorBuffers; i++) { colorBufferTex[i] = null; } - - backupTexture = null; } return outdated; } @@ -416,17 +342,15 @@ public class PFramebuffer implements PConstants { protected void createColorBufferMultisample() { if (screenFb) return; - if (fboMode) { - pg.pushFramebuffer(); - pg.setFramebuffer(this); + pg.pushFramebuffer(); + pg.setFramebuffer(this); - glColorBufferMultisampleID = pg.createRenderBufferObject(); - pgl.glBindRenderbuffer(PGL.GL_RENDERBUFFER, glColorBufferMultisampleID); - pgl.glRenderbufferStorageMultisample(PGL.GL_RENDERBUFFER, nsamples, PGL.GL_RGBA8, width, height); - pgl.glFramebufferRenderbuffer(PGL.GL_FRAMEBUFFER, PGL.GL_COLOR_ATTACHMENT0, PGL.GL_RENDERBUFFER, glColorBufferMultisampleID); - - pg.popFramebuffer(); - } + glColorBufferMultisampleID = pg.createRenderBufferObject(); + pgl.glBindRenderbuffer(PGL.GL_RENDERBUFFER, glColorBufferMultisampleID); + pgl.glRenderbufferStorageMultisample(PGL.GL_RENDERBUFFER, nsamples, PGL.GL_RGBA8, width, height); + pgl.glFramebufferRenderbuffer(PGL.GL_FRAMEBUFFER, PGL.GL_COLOR_ATTACHMENT0, PGL.GL_RENDERBUFFER, glColorBufferMultisampleID); + + pg.popFramebuffer(); } @@ -437,24 +361,22 @@ public class PFramebuffer implements PConstants { throw new RuntimeException("PFramebuffer: size undefined."); } - if (fboMode) { - pg.pushFramebuffer(); - pg.setFramebuffer(this); - - glDepthStencilBufferID = pg.createRenderBufferObject(); - pgl.glBindRenderbuffer(PGL.GL_RENDERBUFFER, glDepthStencilBufferID); - - if (multisample) { - pgl.glRenderbufferStorageMultisample(PGL.GL_RENDERBUFFER, nsamples, PGL.GL_DEPTH24_STENCIL8, width, height); - } else { - pgl.glRenderbufferStorage(PGL.GL_RENDERBUFFER, PGL.GL_DEPTH24_STENCIL8, width, height); - } - - pgl.glFramebufferRenderbuffer(PGL.GL_FRAMEBUFFER, PGL.GL_DEPTH_ATTACHMENT, PGL.GL_RENDERBUFFER, glDepthStencilBufferID); - pgl.glFramebufferRenderbuffer(PGL.GL_FRAMEBUFFER, PGL.GL_STENCIL_ATTACHMENT, PGL.GL_RENDERBUFFER, glDepthStencilBufferID); - - pg.popFramebuffer(); - } + pg.pushFramebuffer(); + pg.setFramebuffer(this); + + glDepthStencilBufferID = pg.createRenderBufferObject(); + pgl.glBindRenderbuffer(PGL.GL_RENDERBUFFER, glDepthStencilBufferID); + + if (multisample) { + pgl.glRenderbufferStorageMultisample(PGL.GL_RENDERBUFFER, nsamples, PGL.GL_DEPTH24_STENCIL8, width, height); + } else { + pgl.glRenderbufferStorage(PGL.GL_RENDERBUFFER, PGL.GL_DEPTH24_STENCIL8, width, height); + } + + pgl.glFramebufferRenderbuffer(PGL.GL_FRAMEBUFFER, PGL.GL_DEPTH_ATTACHMENT, PGL.GL_RENDERBUFFER, glDepthStencilBufferID); + pgl.glFramebufferRenderbuffer(PGL.GL_FRAMEBUFFER, PGL.GL_STENCIL_ATTACHMENT, PGL.GL_RENDERBUFFER, glDepthStencilBufferID); + + pg.popFramebuffer(); } @@ -465,32 +387,30 @@ public class PFramebuffer implements PConstants { throw new RuntimeException("PFramebuffer: size undefined."); } - if (fboMode) { - pg.pushFramebuffer(); - pg.setFramebuffer(this); + pg.pushFramebuffer(); + pg.setFramebuffer(this); - glDepthBufferID = pg.createRenderBufferObject(); - pgl.glBindRenderbuffer(PGL.GL_RENDERBUFFER, glDepthBufferID); + glDepthBufferID = pg.createRenderBufferObject(); + pgl.glBindRenderbuffer(PGL.GL_RENDERBUFFER, glDepthBufferID); - int glConst = PGL.GL_DEPTH_COMPONENT16; - if (depthBits == 16) { - glConst = PGL.GL_DEPTH_COMPONENT16; - } else if (depthBits == 24) { - glConst = PGL.GL_DEPTH_COMPONENT24; - } else if (depthBits == 32) { - glConst = PGL.GL_DEPTH_COMPONENT32; - } - - if (multisample) { - pgl.glRenderbufferStorageMultisample(PGL.GL_RENDERBUFFER, nsamples, glConst, width, height); - } else { - pgl.glRenderbufferStorage(PGL.GL_RENDERBUFFER, glConst, width, height); - } - - pgl.glFramebufferRenderbuffer(PGL.GL_FRAMEBUFFER, PGL.GL_DEPTH_ATTACHMENT, PGL.GL_RENDERBUFFER, glDepthBufferID); - - pg.popFramebuffer(); + int glConst = PGL.GL_DEPTH_COMPONENT16; + if (depthBits == 16) { + glConst = PGL.GL_DEPTH_COMPONENT16; + } else if (depthBits == 24) { + glConst = PGL.GL_DEPTH_COMPONENT24; + } else if (depthBits == 32) { + glConst = PGL.GL_DEPTH_COMPONENT32; } + + if (multisample) { + pgl.glRenderbufferStorageMultisample(PGL.GL_RENDERBUFFER, nsamples, glConst, width, height); + } else { + pgl.glRenderbufferStorage(PGL.GL_RENDERBUFFER, glConst, width, height); + } + + pgl.glFramebufferRenderbuffer(PGL.GL_FRAMEBUFFER, PGL.GL_DEPTH_ATTACHMENT, PGL.GL_RENDERBUFFER, glDepthBufferID); + + pg.popFramebuffer(); } @@ -501,31 +421,29 @@ public class PFramebuffer implements PConstants { throw new RuntimeException("PFramebuffer: size undefined."); } - if (fboMode) { - pg.pushFramebuffer(); - pg.setFramebuffer(this); + pg.pushFramebuffer(); + pg.setFramebuffer(this); - glStencilBufferID = pg.createRenderBufferObject(); - pgl.glBindRenderbuffer(PGL.GL_RENDERBUFFER, glStencilBufferID); + glStencilBufferID = pg.createRenderBufferObject(); + pgl.glBindRenderbuffer(PGL.GL_RENDERBUFFER, glStencilBufferID); - int glConst = PGL.GL_STENCIL_INDEX1; - if (stencilBits == 1) { - glConst = PGL.GL_STENCIL_INDEX1; - } else if (stencilBits == 4) { - glConst = PGL.GL_STENCIL_INDEX4; - } else if (stencilBits == 8) { - glConst = PGL.GL_STENCIL_INDEX8; - } - if (multisample) { - pgl.glRenderbufferStorageMultisample(PGL.GL_RENDERBUFFER, nsamples, glConst, width, height); - } else { - pgl.glRenderbufferStorage(PGL.GL_RENDERBUFFER, glConst, width, height); - } - - pgl.glFramebufferRenderbuffer(PGL.GL_FRAMEBUFFER, PGL.GL_STENCIL_ATTACHMENT, PGL.GL_RENDERBUFFER, glStencilBufferID); - - pg.popFramebuffer(); + int glConst = PGL.GL_STENCIL_INDEX1; + if (stencilBits == 1) { + glConst = PGL.GL_STENCIL_INDEX1; + } else if (stencilBits == 4) { + glConst = PGL.GL_STENCIL_INDEX4; + } else if (stencilBits == 8) { + glConst = PGL.GL_STENCIL_INDEX8; } + if (multisample) { + pgl.glRenderbufferStorageMultisample(PGL.GL_RENDERBUFFER, nsamples, glConst, width, height); + } else { + pgl.glRenderbufferStorage(PGL.GL_RENDERBUFFER, glConst, width, height); + } + + pgl.glFramebufferRenderbuffer(PGL.GL_FRAMEBUFFER, PGL.GL_STENCIL_ATTACHMENT, PGL.GL_RENDERBUFFER, glStencilBufferID); + + pg.popFramebuffer(); } @@ -533,36 +451,4 @@ public class PFramebuffer implements PConstants { pixelBuffer = IntBuffer.allocate(width * height); pixelBuffer.rewind(); } - - /////////////////////////////////////////////////////////// - - // Utilities. - - // Internal copy to texture method. - protected void copyToTexture(IntBuffer buffer, int glid, int gltarget) { - pgl.enableTexturing(gltarget); - pgl.glBindTexture(gltarget, glid); - pgl.glTexSubImage2D(gltarget, 0, 0, 0, width, height, PGL.GL_RGBA, PGL.GL_UNSIGNED_BYTE, buffer); - pgl.glBindTexture(gltarget, 0); - pgl.disableTexturing(gltarget); - } - - public boolean validateFbo() { - int status = pgl.glCheckFramebufferStatus(PGL.GL_FRAMEBUFFER); - if (status == PGL.GL_FRAMEBUFFER_COMPLETE) { - return true; - } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) { - throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT (" + Integer.toHexString(status) + ")"); - } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) { - throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (" + Integer.toHexString(status) + ")"); - } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) { - throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS (" + Integer.toHexString(status) + ")"); - } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_FORMATS) { - throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_FORMATS (" + Integer.toHexString(status) + ")"); - } else if (status == PGL.GL_FRAMEBUFFER_UNSUPPORTED) { - throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_UNSUPPORTED" + Integer.toHexString(status)); - } else { - throw new RuntimeException("PFramebuffer: unknown framebuffer error (" + Integer.toHexString(status) + ")"); - } - } } diff --git a/android/core/src/processing/core/PGL.java b/android/core/src/processing/core/PGL.java index c1e428bcc..1bb38d2dc 100644 --- a/android/core/src/processing/core/PGL.java +++ b/android/core/src/processing/core/PGL.java @@ -25,6 +25,8 @@ package processing.core; import java.nio.Buffer; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.Arrays; @@ -88,11 +90,17 @@ public class PGL { /** Maximum dimension of a texture used to hold font data. **/ public static final int MAX_FONT_TEX_SIZE = 256; + + public static int DEFAULT_DEPTH_BITS = 16; + public static int DEFAULT_STENCIL_BITS = 8; /////////////////////////////////////////////////////////////////////////////////// // OpenGL constants + public static final int GL_FALSE = GLES20.GL_FALSE; + public static final int GL_TRUE = GLES20.GL_TRUE; + public static final int GL_LESS = GLES20.GL_LESS; public static final int GL_LEQUAL = GLES20.GL_LEQUAL; public static final int GL_CCW = GLES20.GL_CCW; @@ -102,8 +110,9 @@ public class PGL { public static final int GL_VIEWPORT = GLES20.GL_VIEWPORT; - public static final int GL_SCISSOR_TEST = GLES20.GL_SCISSOR_TEST; - public static final int GL_DEPTH_TEST = GLES20.GL_DEPTH_TEST; + public static final int GL_SCISSOR_TEST = GLES20.GL_SCISSOR_TEST; + public static final int GL_DEPTH_TEST = GLES20.GL_DEPTH_TEST; + public static final int GL_DEPTH_WRITEMASK = GLES20.GL_DEPTH_WRITEMASK; public static final int GL_COLOR_BUFFER_BIT = GLES20.GL_COLOR_BUFFER_BIT; public static final int GL_DEPTH_BUFFER_BIT = GLES20.GL_DEPTH_BUFFER_BIT; @@ -211,8 +220,13 @@ public class PGL { public static final int GL_READ_FRAMEBUFFER = -1; public static final int GL_DRAW_FRAMEBUFFER = -1; - public static final int GL_VERTEX_SHADER = GLES20.GL_VERTEX_SHADER; - public static final int GL_FRAGMENT_SHADER = GLES20.GL_FRAGMENT_SHADER; + public static final int GL_VERTEX_SHADER = GLES20.GL_VERTEX_SHADER; + public static final int GL_FRAGMENT_SHADER = GLES20.GL_FRAGMENT_SHADER; + public static final int GL_INFO_LOG_LENGTH = GLES20.GL_INFO_LOG_LENGTH; + public static final int GL_SHADER_SOURCE_LENGTH = GLES20.GL_SHADER_SOURCE_LENGTH; + public static final int GL_COMPILE_STATUS = GLES20.GL_COMPILE_STATUS; + public static final int GL_LINK_STATUS = GLES20.GL_LINK_STATUS; + public static final int GL_VALIDATE_STATUS = GLES20.GL_VALIDATE_STATUS; public static final int GL_MULTISAMPLE = -1; public static final int GL_POINT_SMOOTH = -1; @@ -238,12 +252,60 @@ public class PGL { /** The renderer object driving the rendering loop, * analogous to the GLEventListener in JOGL */ protected AndroidRenderer renderer; + + /////////////////////////////////////////////////////////////////////////////////// + + // FBO for incremental drawing + + protected boolean firstOnscreenFrame = true; + protected int[] textures = { 0, 0 }; + protected int[] fbo = { 0 }; + protected int[] depth = { 0 }; + protected int[] stencil = { 0 }; + protected int texWidth, texHeight; + protected int backTex, frontTex; + + /////////////////////////////////////////////////////////////////////////////////// + + // Texture rendering + + protected boolean loadedTexShader = false; + protected int texShaderProgram; + protected int texVertShader; + protected int texFragShader; + + protected int texVertLoc; + protected int texTCoordLoc; + + protected float[] texCoords = { + // X, Y, U, V + -1.0f, -1.0f, 0.0f, 0.0f, + +1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, +1.0f, 0.0f, 1.0f, + +1.0f, +1.0f, 1.0f, 1.0f + }; + protected FloatBuffer texData; + + protected String texVertShaderSource = "attribute vec2 inVertex;" + + "attribute vec2 inTexcoord;" + + "varying vec2 vertTexcoord;" + + "void main() {" + + " gl_Position = vec4(inVertex, 0, 1);" + + " vertTexcoord = inTexcoord;" + + "}"; + + protected String texFragShaderSource = "precision mediump float;" + + "uniform sampler2D textureSampler;" + + "varying vec2 vertTexcoord;" + + "void main() {" + + " gl_FragColor = texture2D(textureSampler, vertTexcoord.st);" + + "}"; /////////////////////////////////////////////////////////////////////////////////// // Intialization, finalization - // TODO: implement double buffering support in onscreen rendering, offscreen rendering. + // TODO: implement double buffering support in offscreen rendering. public PGL(PGraphicsAndroid3D pg) { @@ -259,6 +321,52 @@ public class PGL { public void updatePrimary() { + if (!initialized) { + String ext = GLES20.glGetString(GLES20.GL_EXTENSIONS); + if (-1 < ext.indexOf("texture_non_power_of_two")) { + texWidth = pg.width; + texHeight = pg.height; + } else { + texWidth = PGL.nextPowerOfTwo(pg.width); + texHeight = PGL.nextPowerOfTwo(pg.height); + } + + // We create the GL resources we need to draw incrementally, ie: not clearing + // the screen in each frame. Because the way Android handles double buffering + // we need to handle our own custom buffering using FBOs. + GLES20.glGenTextures(2, textures, 0); + for (int i = 0; i < 2; i++) { + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i]); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, texWidth, texHeight, 0, PGL.GL_RGBA, PGL.GL_UNSIGNED_BYTE, null); + initTexture(GLES20.GL_TEXTURE_2D, texWidth, texHeight, PGL.GL_RGBA, PGL.GL_UNSIGNED_BYTE); + } + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + + GLES20.glGenFramebuffers(1, fbo, 0); + GLES20.glGenRenderbuffers(1, depth, 0); + GLES20.glGenRenderbuffers(1, stencil, 0); + + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo[0]); + + GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, depth[0]); + GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, texWidth, texHeight); + GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, depth[0]); + + GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, stencil[0]); + GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_STENCIL_INDEX8, texWidth, texHeight); + GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_STENCIL_ATTACHMENT, GLES20.GL_RENDERBUFFER, stencil[0]); + + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + + backTex = 1; + frontTex = 0; + + initialized = true; + } } @@ -266,50 +374,9 @@ public class PGL { gl = primary.gl; } + public void initPrimarySurface(int antialias) { - /* - - offscreenTexCrop = new int[4]; - offscreenTexCrop[0] = 0; - offscreenTexCrop[1] = 0; - offscreenTexCrop[2] = width; - offscreenTexCrop[3] = height; - - offscreenImages = new PImage[2]; - offscreenParams = new PTexture.Parameters[2]; - - // Nearest filtering is used for the primary surface, otherwise some - // artifacts appear (diagonal line when blending, for instance). This - // might deserve further examination. - offscreenParams[0] = new PTexture.Parameters(ARGB, POINT); - offscreenParams[1] = new PTexture.Parameters(ARGB, POINT); - offscreenImages[0] = parent.createImage(width, height, ARGB, offscreenParams[0]); - offscreenImages[1] = parent.createImage(width, height, ARGB, offscreenParams[1]); - - offscreenTextures = new PTexture[2]; - offscreenTextures[0] = addTexture(offscreenImages[0]); - offscreenTextures[1] = addTexture(offscreenImages[1]); - - // Drawing textures are marked as flipped along Y to ensure they are properly - // rendered by Processing, which has inverted Y axis with respect to - // OpenGL. - offscreenTextures[0].setFlippedY(true); - offscreenTextures[1].setFlippedY(true); - - offscreenIndex = 0; - - offscreenFramebuffer = new PFramebuffer(parent, offscreenTextures[0].glWidth, offscreenTextures[0].glHeight, - 1, 1, offscreenDepthBits, offscreenStencilBits, false); - - // The image texture points to the current offscreen texture. - texture = offscreenTextures[offscreenIndex]; - this.setCache(a3d, offscreenTextures[offscreenIndex]); - this.setParams(a3d, offscreenParams[offscreenIndex]); - - */ - - initialized = true; } @@ -332,6 +399,7 @@ public class PGL { offscreenImages[0] = parent.createImage(width, height, ARGB, offscreenParams[0]); offscreenImages[1] = parent.createImage(width, height, ARGB, offscreenParams[1]); + offscreenTextures = new PTexture[2]; offscreenTextures[0] = addTexture(offscreenImages[0]); offscreenTextures[1] = addTexture(offscreenImages[1]); @@ -344,6 +412,10 @@ public class PGL { offscreenIndex = 0; + + + + offscreenFramebuffer = new PFramebuffer(parent, offscreenTextures[0].glWidth, offscreenTextures[0].glHeight, 1, 1, offscreenDepthBits, offscreenStencilBits, false); @@ -363,86 +435,70 @@ public class PGL { // Frame rendering - public void beginOnscreenDraw(boolean clear, int frame) { - GLES20.glClearColor(0, 0, 0, 0); - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); - - /* + public void beginOnscreenDraw(boolean clear) { if (clear) { // Simplest scenario: clear mode means we clear both the color and depth buffers. // No need for saving front color buffer, etc. - gl.glClearColor(0, 0, 0, 0); - gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); + GLES20.glClearColor(0, 0, 0, 0); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); } else { - // We need to save the color buffer after finishing with the rendering of this frame, - // to use is as the background for the next frame (I call this "incremental rendering"). - - if (fboSupported) { - if (offscreenFramebuffer != null) { - // Setting the framebuffer corresponding to this surface. - pushFramebuffer(); - setFramebuffer(offscreenFramebuffer); - // Setting the current front color buffer. - offscreenFramebuffer.setColorBuffer(offscreenTextures[offscreenIndex]); - - // Drawing contents of back color buffer as background. - gl.glClearColor(0, 0, 0, 0); - if (frame == 0) { - // No need to draw back color buffer because we are in the first frame ever. - gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); - } else { - gl.glClear(GL10.GL_DEPTH_BUFFER_BIT); - // Render previous draw texture as background. - drawOffscreenTexture((offscreenIndex + 1) % 2); - } - } - } else { - if (texture != null) { - gl.glClearColor(0, 0, 0, 0); - gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); - if (0 < frame) { - drawTexture(); - } - } + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo[0]); + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textures[frontTex], 0); + + int status = glCheckFramebufferStatus(PGL.GL_FRAMEBUFFER); + if (status != PGL.GL_FRAMEBUFFER_COMPLETE) { + if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT (" + Integer.toHexString(status) + ")"); + } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (" + Integer.toHexString(status) + ")"); + } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS (" + Integer.toHexString(status) + ")"); + } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_FORMATS) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_FORMATS (" + Integer.toHexString(status) + ")"); + } else if (status == PGL.GL_FRAMEBUFFER_UNSUPPORTED) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_UNSUPPORTED" + Integer.toHexString(status)); + } else { + throw new RuntimeException("PFramebuffer: unknown framebuffer error (" + Integer.toHexString(status) + ")"); + } } - } - */ + + // We need to save the color buffer after finishing with the rendering of this frame, + // to use is as the background for the next frame ("incremental drawing"). + GLES20.glClearColor(0, 0, 0, 0); + GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_STENCIL_BUFFER_BIT); + if (firstOnscreenFrame) { + // No need to draw back color buffer because we are in the first frame. + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + firstOnscreenFrame = false; + } else { + // Render previous draw texture as background. + drawTexture(GLES20.GL_TEXTURE_2D, textures[backTex], texWidth, texHeight, 0, 0, pg.width, pg.height, 0, 0, pg.width, pg.height); + } + } } public void endOnscreenDraw(boolean clear0) { - /* - if (!clear0) { - // 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 - // for incremental rendering. Depending on whether or not FBOs are supported, - // one of the two following paths is selected. - if (fboSupported) { - if (offscreenFramebuffer != null) { - // Restoring screen buffer. - popFramebuffer(); + if (!clear0) { + // 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); - // Only the primary surface in clear mode will write the contents of the - // offscreen framebuffer to the screen. - gl.glClearColor(0, 0, 0, 0); - gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); + GLES20.glClearColor(0, 0, 0, 0); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); - // Render current draw texture to screen. - drawOffscreenTexture(offscreenIndex); - - swapOffscreenIndex(); - } - } else { - if (texture != null) { - copyFrameToTexture(); - } - } - } - */ + // Render current front texture to screen. + drawTexture(GLES20.GL_TEXTURE_2D, textures[frontTex], texWidth, texHeight, 0, 0, pg.width, pg.height, 0, 0, pg.width, pg.height); + + // Swapping front and back textures. + int temp = frontTex; + frontTex = backTex; + backTex = temp; + } } - public void beginOffscreenDraw(boolean clear, int frame) { + public void beginOffscreenDraw(boolean clear) { /* // Drawing contents of back color buffer as background. gl.glClearColor(0, 0, 0, 0); @@ -494,6 +550,15 @@ public class PGL { } + public void glGetBooleanv(int name, boolean[] values, int offset) { + if (-1 < name) { + GLES20.glGetBooleanv(name, values, offset); + } else { + Arrays.fill(values, false); + } + } + + /////////////////////////////////////////////////////////////////////////////////// // Enable/disable caps @@ -643,6 +708,11 @@ public class PGL { } + public void glDrawArrays(int mode, int first, int count) { + GLES20.glDrawArrays(mode, first, count); + } + + public void glDrawElements(int mode, int count, int type, int offset) { GLES20.glDrawElements(mode, count, type, offset); } @@ -659,7 +729,12 @@ public class PGL { public void glVertexAttribPointer(int loc, int size, int type, boolean normalized, int stride, int offset) { - GLES20.glVertexAttribPointer(loc, size, type, normalized, stride, offset); + GLES20.glVertexAttribPointer(loc, size, type, normalized, stride, offset); + } + + + public void glVertexAttribPointer(int loc, int size, int type, boolean normalized, int stride, Buffer data) { + GLES20.glVertexAttribPointer(loc, size, type, normalized, stride, data); } @@ -890,6 +965,26 @@ public class PGL { } + public void glGetShaderiv(int shader, int pname, int[] params, int offset) { + GLES20.glGetShaderiv(shader, pname, params, offset); + } + + + public String glGetShaderInfoLog(int shader) { + return GLES20.glGetShaderInfoLog(shader); + } + + + public void glGetProgramiv(int prog, int pname, int[] params, int offset) { + GLES20.glGetProgramiv(prog, pname, params, offset); + } + + + public String glGetProgramInfoLog(int prog) { + return GLES20.glGetProgramInfoLog(prog); + } + + ///////////////////////////////////////////////////////////////////////////////// // Viewport @@ -1107,17 +1202,160 @@ public class PGL { } } } + + + public void drawTexture(int target, int id, int width, int height, + int texX0, int texY0, int texX1, int texY1, + int scrX0, int scrY0, int scrX1, int scrY1) { - - public String getShaderLog(int id) { - int[] compiled = new int[1]; - GLES20.glGetShaderiv(id, GLES20.GL_COMPILE_STATUS, compiled, 0); - if (compiled[0] == 0) { - return GLES20.glGetShaderInfoLog(id); - } else { - return ""; + if (!loadedTexShader) { + texVertShader = createShader(GL_VERTEX_SHADER, texVertShaderSource); + texFragShader = createShader(GL_FRAGMENT_SHADER, texFragShaderSource); + if (0 < texVertShader && 0 < texFragShader) { + texShaderProgram = createProgram(texVertShader, texFragShader); + } + if (0 < texShaderProgram) { + texVertLoc = glGetAttribLocation(texShaderProgram, "inVertex"); + texTCoordLoc = glGetAttribLocation(texShaderProgram, "inTexcoord"); + } + texData = ByteBuffer.allocateDirect(texCoords.length * SIZEOF_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer(); + loadedTexShader = true; } - } + + if (0 < texShaderProgram) { + // When drawing the texture we don't write to the + // depth mask, so the texture remains in the background + // and can be occluded by anything drawn later, even if + // if it is behind it. + boolean[] val = new boolean[1]; + glGetBooleanv(GL_DEPTH_WRITEMASK, val, 0); + boolean writeMask = val[0]; + glDepthMask(false); + + glUseProgram(texShaderProgram); + + glEnableVertexAttribArray(texVertLoc); + glEnableVertexAttribArray(texTCoordLoc); + + // Vertex coordinates of the textured quad are specified + // in normalized screen space (-1, 1): + + // Corner 1 + texCoords[ 0] = 2 * (float)scrX0 / pg.width - 1; + texCoords[ 1] = 2 * (float)scrY0 / pg.height - 1; + texCoords[ 2] = (float)texX0 / width; + texCoords[ 3] = (float)texY0 / height; + + // Corner 2 + texCoords[ 4] = 2 * (float)scrX1 / pg.width - 1; + texCoords[ 5] = 2 * (float)scrY0 / pg.height - 1; + texCoords[ 6] = (float)texX1 / width; + texCoords[ 7] = (float)texY0 / height; + + // Corner 3 + texCoords[ 8] = 2 * (float)scrX0 / pg.width - 1; + texCoords[ 9] = 2 * (float)scrY1 / pg.height - 1; + texCoords[10] = (float)texX0 / width; + texCoords[11] = (float)texY1 / height; + + // Corner 4 + texCoords[12] = 2 * (float)scrX1 / pg.width - 1; + texCoords[13] = 2 * (float)scrY1 / pg.height - 1; + texCoords[14] = (float)texX1 / width; + texCoords[15] = (float)texY1 / height; + + texData.rewind(); + texData.put(texCoords); + + enableTexturing(target); + glActiveTexture(target); + glBindTexture(target, id); + + texData.position(0); + glVertexAttribPointer(texVertLoc, 2, GL_FLOAT, false, 4 * SIZEOF_FLOAT, texData); + texData.position(2); + glVertexAttribPointer(texTCoordLoc, 2, GL_FLOAT, false, 4 * SIZEOF_FLOAT, texData); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glBindTexture(target, 0); + disableTexturing(target); + + glDisableVertexAttribArray(texVertLoc); + glDisableVertexAttribArray(texTCoordLoc); + + glUseProgram(0); + + glDepthMask(writeMask); + } + } + + + // bit shifting this might be more efficient + static public int nextPowerOfTwo(int val) { + int ret = 1; + while (ret < val) { + ret <<= 1; + } + return ret; + } + + + public int createShader(int shaderType, String source) { + int shader = glCreateShader(shaderType); + if (shader != 0) { + glShaderSource(shader, source); + glCompileShader(shader); + int[] compiled = new int[1]; + glGetShaderiv(shader, GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == GL_FALSE) { + System.err.println("Could not compile shader " + shaderType + ":"); + System.err.println(glGetShaderInfoLog(shader)); + glDeleteShader(shader); + shader = 0; + } + } + return shader; + } + + + public int createProgram(int vertexShader, int fragmentShader) { + int program = glCreateProgram(); + if (program != 0) { + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + int[] linked = new int[1]; + glGetProgramiv(program, GL_LINK_STATUS, linked, 0); + if (linked[0] == GL_FALSE) { + System.err.println("Could not link program: "); + System.err.println(glGetProgramInfoLog(program)); + glDeleteProgram(program); + program = 0; + } + } + return program; + } + + + public boolean validateFramebuffer() { + int status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status == GL_FRAMEBUFFER_COMPLETE) { + return true; + } else if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT (" + Integer.toHexString(status) + ")"); + } else if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (" + Integer.toHexString(status) + ")"); + } else if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS (" + Integer.toHexString(status) + ")"); + } else if (status == GL_FRAMEBUFFER_INCOMPLETE_FORMATS) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_FORMATS (" + Integer.toHexString(status) + ")"); + } else if (status == GL_FRAMEBUFFER_UNSUPPORTED) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_UNSUPPORTED" + Integer.toHexString(status)); + } else { + throw new RuntimeException("PFramebuffer: unknown framebuffer error (" + Integer.toHexString(status) + ")"); + } + } ///////////////////////////////////////////////////////////////////////////////// @@ -1280,9 +1518,9 @@ public class PGL { alphaBits = a; depthBits = d; stencilBits = s; - - pg.offscreenDepthBits = d; - pg.offscreenStencilBits = s; + + DEFAULT_DEPTH_BITS = d; + DEFAULT_STENCIL_BITS = s; } } } diff --git a/android/core/src/processing/core/PGraphicsAndroid3D.java b/android/core/src/processing/core/PGraphicsAndroid3D.java index eff1c7506..347ba3578 100644 --- a/android/core/src/processing/core/PGraphicsAndroid3D.java +++ b/android/core/src/processing/core/PGraphicsAndroid3D.java @@ -42,7 +42,7 @@ import java.util.Stack; */ public class PGraphicsAndroid3D extends PGraphics { /** Interface between Processing and OpenGL */ - protected PGL pgl; + public PGL pgl; /** The PApplet renderer. For the primary surface, pg == this. */ protected PGraphicsAndroid3D pg; @@ -83,8 +83,6 @@ public class PGraphicsAndroid3D extends PGraphics { /** Extensions used by Processing */ static public boolean npotTexSupported; static public boolean mipmapGeneration; - static public boolean vboSupported; - static public boolean fboSupported; static public boolean fboMultisampleSupported; static public boolean blendEqSupported; @@ -291,10 +289,6 @@ public class PGraphicsAndroid3D extends PGraphics { protected PFramebuffer offscreenFramebufferMultisample; protected boolean offscreenMultisample; - /** These are public so they can be changed by advanced users. */ - public int offscreenDepthBits = 24; - public int offscreenStencilBits = 8; - // ........................................................ // Utility variables: @@ -1507,17 +1501,17 @@ public class PGraphicsAndroid3D extends PGraphics { pgl.glClearColor(0, 0, 0, 0); pgl.glClear(PGL.GL_DEPTH_BUFFER_BIT | PGL.GL_STENCIL_BUFFER_BIT); + if (primarySurface) { + pgl.beginOnscreenDraw(clearColorBuffer); + } else { + pgl.beginOffscreenDraw(clearColorBuffer); + } + if (hints[DISABLE_DEPTH_MASK]) { pgl.glDepthMask(false); } else { pgl.glDepthMask(true); - } - - if (primarySurface) { - pgl.beginOnscreenDraw(clearColorBuffer, parent.frameCount); - } else { - pgl.beginOffscreenDraw(clearColorBuffer, parent.frameCount); - } + } drawing = true; @@ -1549,8 +1543,8 @@ public class PGraphicsAndroid3D extends PGraphics { pgl.glViewport(savedViewport[0], savedViewport[1], savedViewport[2], savedViewport[3]); if (primarySurface) { - pgl.glFlush(); pgl.endOnscreenDraw(clearColorBuffer0); + pgl.glFlush(); } else { if (offscreenMultisample) { offscreenFramebufferMultisample.copy(offscreenFramebuffer); @@ -5602,8 +5596,8 @@ public class PGraphicsAndroid3D extends PGraphics { // function used in multisampled (antialiased) offscreen rendering. if (PGraphicsAndroid3D.fboMultisampleSupported && 1 < antialias) { offscreenFramebufferMultisample = new PFramebuffer(parent, texture.glWidth, texture.glHeight, antialias, 0, - offscreenDepthBits, offscreenStencilBits, - offscreenDepthBits == 24 && offscreenStencilBits == 8, false); + PGL.DEFAULT_DEPTH_BITS, PGL.DEFAULT_STENCIL_BITS, + PGL.DEFAULT_DEPTH_BITS == 24 && PGL.DEFAULT_STENCIL_BITS == 8, false); offscreenFramebufferMultisample.clear(); offscreenMultisample = true; @@ -5617,8 +5611,8 @@ public class PGraphicsAndroid3D extends PGraphics { } else { antialias = 0; offscreenFramebuffer = new PFramebuffer(parent, texture.glWidth, texture.glHeight, 1, 1, - offscreenDepthBits, offscreenStencilBits, - offscreenDepthBits == 24 && offscreenStencilBits == 8, false); + PGL.DEFAULT_DEPTH_BITS, PGL.DEFAULT_STENCIL_BITS, + PGL.DEFAULT_DEPTH_BITS == 24 && PGL.DEFAULT_STENCIL_BITS == 8, false); offscreenMultisample = false; } @@ -5635,8 +5629,6 @@ public class PGraphicsAndroid3D extends PGraphics { npotTexSupported = -1 < OPENGL_EXTENSIONS.indexOf("texture_non_power_of_two"); mipmapGeneration = -1 < OPENGL_EXTENSIONS.indexOf("generate_mipmap"); - vboSupported = -1 < OPENGL_EXTENSIONS.indexOf("vertex_buffer_object"); - fboSupported = -1 < OPENGL_EXTENSIONS.indexOf("framebuffer_object"); fboMultisampleSupported = -1 < OPENGL_EXTENSIONS.indexOf("framebuffer_multisample"); try { diff --git a/android/core/src/processing/core/PShader.java b/android/core/src/processing/core/PShader.java index 8f9b7373d..1660d7530 100644 --- a/android/core/src/processing/core/PShader.java +++ b/android/core/src/processing/core/PShader.java @@ -26,6 +26,7 @@ package processing.core; import java.io.IOException; import java.net.URL; + /** * This class encapsulates a GLSL shader program, including a vertex * and a fragment shader. Originally based in the code by JohnG @@ -42,6 +43,9 @@ public class PShader { protected String vertexFilename; protected String fragmentFilename; + + protected String vertexShaderSource; + protected String fragmentShaderSource; protected int programObject; protected int vertexShader; @@ -292,32 +296,61 @@ public class PShader { } protected void init() { - if (programObject == 0 || contextIsOutdated()) { - + if (programObject == 0 || contextIsOutdated()) { context = pgl.getContext(); programObject = pg.createGLSLProgramObject(); + boolean hasVert = false; if (vertexFilename != null) { - loadVertexShader(vertexFilename); + hasVert = loadVertexShader(vertexFilename); } else if (vertexURL != null) { - loadVertexShader(vertexURL); + hasVert = loadVertexShader(vertexURL); } else { PGraphics.showException("Vertex shader filenames and URLs are both null!"); } + boolean hasFrag = false; if (fragmentFilename != null) { - loadFragmentShader(fragmentFilename); + hasFrag = loadFragmentShader(fragmentFilename); } else if (fragmentURL != null) { - loadFragmentShader(fragmentURL); + hasFrag = loadFragmentShader(fragmentURL); } else { PGraphics.showException("Fragment shader filenames and URLs are both null!"); } - checkLogInfo("Vertex shader " + vertexFilename + " compilation: ", vertexShader); - checkLogInfo("Fragment shader " + fragmentFilename + " compilation: ", fragmentShader); + boolean vertRes = true; + if (hasVert) { + vertRes = compileVertexShader(); + } + + boolean fragRes = true; + if (hasFrag) { + fragRes = compileFragmentShader(); + } - pgl.glLinkProgram(programObject); - pgl.glValidateProgram(programObject); + if (vertRes && fragRes) { + if (hasVert) { + pgl.glAttachShader(programObject, vertexShader); + } + if (hasFrag) { + pgl.glAttachShader(programObject, fragmentShader); + } + pgl.glLinkProgram(programObject); + + int[] linked = new int[1]; + pgl.glGetProgramiv(programObject, PGL.GL_LINK_STATUS, linked, 0); + if (linked[0] == PGL.GL_FALSE) { + PGraphics.showException("Cannot link shader program:\n" + pgl.glGetProgramInfoLog(programObject)); + } + + pgl.glValidateProgram(programObject); + + int[] validated = new int[1]; + pgl.glGetProgramiv(programObject, PGL.GL_VALIDATE_STATUS, validated, 0); + if (validated[0] == PGL.GL_FALSE) { + PGraphics.showException("Cannot validate shader program:\n" + pgl.glGetProgramInfoLog(programObject)); + } + } } } @@ -338,9 +371,9 @@ public class PShader { * * @param file String */ - protected void loadVertexShader(String filename) { - String shaderSource = PApplet.join(parent.loadStrings(filename), "\n"); - attachVertexShader(shaderSource); + protected boolean loadVertexShader(String filename) { + vertexShaderSource = PApplet.join(parent.loadStrings(filename), "\n"); + return vertexShaderSource != null; } /** @@ -348,12 +381,13 @@ public class PShader { * * @param file String */ - protected void loadVertexShader(URL url) { + protected boolean loadVertexShader(URL url) { try { - String shaderSource = PApplet.join(PApplet.loadStrings(url.openStream()), "\n"); - attachVertexShader(shaderSource); + vertexShaderSource = PApplet.join(PApplet.loadStrings(url.openStream()), "\n"); + return vertexShaderSource != null; } catch (IOException e) { - PGraphics.showException("Cannot load shader " + url.getFile()); + PGraphics.showException("Cannot load vertex shader " + url.getFile()); + return false; } } @@ -362,9 +396,9 @@ public class PShader { * * @param file String */ - protected void loadFragmentShader(String filename) { - String shaderSource = PApplet.join(parent.loadStrings(filename), "\n"); - attachFragmentShader(shaderSource); + protected boolean loadFragmentShader(String filename) { + fragmentShaderSource = PApplet.join(parent.loadStrings(filename), "\n"); + return fragmentShaderSource != null; } /** @@ -372,51 +406,53 @@ public class PShader { * * @param url URL */ - protected void loadFragmentShader(URL url) { + protected boolean loadFragmentShader(URL url) { try { - String shaderSource = PApplet.join(PApplet.loadStrings(url.openStream()), "\n"); - attachFragmentShader(shaderSource); + fragmentShaderSource = PApplet.join(PApplet.loadStrings(url.openStream()), "\n"); + return fragmentShaderSource != null; } catch (IOException e) { - PGraphics.showException("Cannot load shader " + url.getFile()); + PGraphics.showException("Cannot load fragment shader " + url.getFile()); + return false; } } /** * @param shaderSource a string containing the shader's code */ - protected void attachVertexShader(String shaderSource) { + protected boolean compileVertexShader() { vertexShader = pg.createGLSLVertShaderObject(); - pgl.glShaderSource(vertexShader, shaderSource); + pgl.glShaderSource(vertexShader, vertexShaderSource); pgl.glCompileShader(vertexShader); - - pgl.glAttachShader(programObject, vertexShader); + + int[] compiled = new int[1]; + pgl.glGetShaderiv(vertexShader, PGL.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == PGL.GL_FALSE) { + PGraphics.showException("Cannot compile vertex shader:\n" + pgl.glGetShaderInfoLog(vertexShader)); + return false; + } else { + return true; + } } /** * @param shaderSource a string containing the shader's code */ - protected void attachFragmentShader(String shaderSource) { + protected boolean compileFragmentShader() { fragmentShader = pg.createGLSLFragShaderObject(); - pgl.glShaderSource(fragmentShader, shaderSource); + pgl.glShaderSource(fragmentShader, fragmentShaderSource); pgl.glCompileShader(fragmentShader); - - pgl.glAttachShader(programObject, fragmentShader); - } - - /** - * Check the log error for the opengl object obj. Prints error - * message if needed. - */ - protected void checkLogInfo(String title, int obj) { - String log = pgl.getShaderLog(obj); - if (!log.equals("")) { - System.out.println(title); - System.out.println(log); - } + int[] compiled = new int[1]; + pgl.glGetShaderiv(fragmentShader, PGL.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == PGL.GL_FALSE) { + PGraphics.showException("Cannot compile fragment shader:\n" + pgl.glGetShaderInfoLog(fragmentShader)); + return false; + } else { + return true; + } } @@ -434,4 +470,4 @@ public class PShader { programObject = 0; } } -} +} \ No newline at end of file diff --git a/android/core/src/processing/core/PTexture.java b/android/core/src/processing/core/PTexture.java index a835abc70..9ec467705 100644 --- a/android/core/src/processing/core/PTexture.java +++ b/android/core/src/processing/core/PTexture.java @@ -289,23 +289,13 @@ public class PTexture implements PConstants { tempFbo = new PFramebuffer(parent, glWidth, glHeight); } - if (PGraphicsAndroid3D.fboSupported) { - // 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(); - } else { - // Here we don't have FBOs, so the method above is of no use. What we do instead is - // to draw the texture to the screen framebuffer, and then grab the pixels from there. - pg.pushFramebuffer(); - pg.setFramebuffer(tempFbo); - pg.drawTexture(this, 0, 0, glWidth, glHeight, 0, 0, glWidth, glHeight); - tempFbo.readPixels(); - pg.popFramebuffer(); - } + // 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(); if (tempPixels == null) { tempPixels = new int[size]; @@ -484,15 +474,6 @@ public class PTexture implements PConstants { //////////////////////////////////////////////////////////// // Utilities - - // bit shifting this might be more efficient - protected int nextPowerOfTwo(int val) { - int ret = 1; - while (ret < val) { - ret <<= 1; - } - return ret; - } /** @@ -741,8 +722,8 @@ public class PTexture implements PConstants { glWidth = w; glHeight = h; } else { - glWidth = nextPowerOfTwo(w); - glHeight = nextPowerOfTwo(h); + glWidth = PGL.nextPowerOfTwo(w); + glHeight = PGL.nextPowerOfTwo(h); } if ((glWidth > PGraphicsAndroid3D.maxTextureSize) || (glHeight > PGraphicsAndroid3D.maxTextureSize)) { diff --git a/java/libraries/opengl/src/processing/opengl/FillShaderFragTex.glsl b/java/libraries/opengl/src/processing/opengl/FillShaderFragTex.glsl index 5b4d09298..650e90355 100644 --- a/java/libraries/opengl/src/processing/opengl/FillShaderFragTex.glsl +++ b/java/libraries/opengl/src/processing/opengl/FillShaderFragTex.glsl @@ -21,6 +21,8 @@ uniform sampler2D textureSampler; +uniform vec2 texcoordOffset; + varying vec4 vertColor; varying vec4 vertTexcoord; diff --git a/java/libraries/opengl/src/processing/opengl/PFramebuffer.java b/java/libraries/opengl/src/processing/opengl/PFramebuffer.java index d0a726880..15f7e84a8 100644 --- a/java/libraries/opengl/src/processing/opengl/PFramebuffer.java +++ b/java/libraries/opengl/src/processing/opengl/PFramebuffer.java @@ -264,7 +264,7 @@ public class PFramebuffer implements PConstants { pgl.glFramebufferTexture2D(PGL.GL_FRAMEBUFFER, PGL.GL_COLOR_ATTACHMENT0 + i, colorBufferTex[i].glTarget, colorBufferTex[i].glID, 0); } - validateFbo(); + pgl.validateFramebuffer(); pg.popFramebuffer(); } @@ -457,36 +457,4 @@ public class PFramebuffer implements PConstants { pixelBuffer = IntBuffer.allocate(width * height); pixelBuffer.rewind(); } - - /////////////////////////////////////////////////////////// - - // Utilities. - - // Internal copy to texture method. - protected void copyToTexture(IntBuffer buffer, int glid, int gltarget) { - pgl.enableTexturing(gltarget); - pgl.glBindTexture(gltarget, glid); - pgl.glTexSubImage2D(gltarget, 0, 0, 0, width, height, PGL.GL_RGBA, PGL.GL_UNSIGNED_BYTE, buffer); - pgl.glBindTexture(gltarget, 0); - pgl.disableTexturing(gltarget); - } - - public boolean validateFbo() { - int status = pgl.glCheckFramebufferStatus(PGL.GL_FRAMEBUFFER); - if (status == PGL.GL_FRAMEBUFFER_COMPLETE) { - return true; - } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) { - throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT (" + Integer.toHexString(status) + ")"); - } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) { - throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (" + Integer.toHexString(status) + ")"); - } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) { - throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS (" + Integer.toHexString(status) + ")"); - } else if (status == PGL.GL_FRAMEBUFFER_INCOMPLETE_FORMATS) { - throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_FORMATS (" + Integer.toHexString(status) + ")"); - } else if (status == PGL.GL_FRAMEBUFFER_UNSUPPORTED) { - throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_UNSUPPORTED" + Integer.toHexString(status)); - } else { - throw new RuntimeException("PFramebuffer: unknown framebuffer error (" + Integer.toHexString(status) + ")"); - } - } } diff --git a/java/libraries/opengl/src/processing/opengl/PGL.java b/java/libraries/opengl/src/processing/opengl/PGL.java index 5bea8d280..6375c335b 100644 --- a/java/libraries/opengl/src/processing/opengl/PGL.java +++ b/java/libraries/opengl/src/processing/opengl/PGL.java @@ -28,7 +28,10 @@ import java.awt.Canvas; import java.nio.Buffer; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; import java.nio.IntBuffer; +import java.util.Arrays; import java.util.Timer; import java.util.TimerTask; @@ -117,8 +120,9 @@ public class PGL { public static final int GL_VIEWPORT = GL.GL_VIEWPORT; - public static final int GL_SCISSOR_TEST = GL.GL_SCISSOR_TEST; - public static final int GL_DEPTH_TEST = GL.GL_DEPTH_TEST; + public static final int GL_SCISSOR_TEST = GL.GL_SCISSOR_TEST; + public static final int GL_DEPTH_TEST = GL.GL_DEPTH_TEST; + public static final int GL_DEPTH_WRITEMASK = GL.GL_DEPTH_WRITEMASK; public static final int GL_COLOR_BUFFER_BIT = GL.GL_COLOR_BUFFER_BIT; public static final int GL_DEPTH_BUFFER_BIT = GL.GL_DEPTH_BUFFER_BIT; @@ -287,6 +291,41 @@ public class PGL { /** Desired target framerate */ protected float targetFramerate = 60; protected boolean setFramerate = false; + + /////////////////////////////////////////////////////////////////////////////////// + + // Texture rendering + + protected boolean loadedTexShader = false; + protected int texShaderProgram; + protected int texVertShader; + protected int texFragShader; + + protected int texVertLoc; + protected int texTCoordLoc; + + protected float[] texCoords = { + // X, Y, U, V + -1.0f, -1.0f, 0.0f, 0.0f, + +1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, +1.0f, 0.0f, 1.0f, + +1.0f, +1.0f, 1.0f, 1.0f + }; + protected FloatBuffer texData; + + protected String texVertShaderSource = "attribute vec2 inVertex;" + + "attribute vec2 inTexcoord;" + + "varying vec2 vertTexcoord;" + + "void main() {" + + " gl_Position = vec4(inVertex, 0, 1);" + + " vertTexcoord = inTexcoord;" + + "}"; + + protected String texFragShaderSource = "uniform sampler2D textureSampler;" + + "varying vec2 vertTexcoord;" + + "void main() {" + + " gl_FragColor = texture2D(textureSampler, vertTexcoord.st);" + + "}"; /////////////////////////////////////////////////////////////////////////////////// @@ -408,7 +447,7 @@ public class PGL { // Frame rendering - public void beginOnscreenDraw(boolean clear, int frame) { + public void beginOnscreenDraw(boolean clear) { } @@ -416,7 +455,7 @@ public class PGL { } - public void beginOffscreenDraw(boolean clear, int frame) { + public void beginOffscreenDraw(boolean clear) { } @@ -455,6 +494,19 @@ public class PGL { } + public void glGetBooleanv(int name, boolean[] values, int offset) { + if (-1 < name) { + byte[] bvalues = new byte[values.length]; + gl.glGetBooleanv(name, bvalues, offset); + for (int i = 0; i < values.length; i++) { + values[i] = bvalues[i] != 0; + } + } else { + Arrays.fill(values, false); + } + } + + /////////////////////////////////////////////////////////////////////////////////// // Enable/disable caps @@ -604,6 +656,11 @@ public class PGL { } + public void glDrawArrays(int mode, int first, int count) { + gl.glDrawArrays(mode, first, count); + } + + public void glDrawElements(int mode, int count, int type, int offset) { gl.glDrawElements(mode, count, type, offset); } @@ -624,6 +681,11 @@ public class PGL { } + public void glVertexAttribPointer(int loc, int size, int type, boolean normalized, int stride, Buffer data) { + gl2.glVertexAttribPointer(loc, size, type, normalized, stride, data); + } + + public ByteBuffer glMapBuffer(int target, int access) { return gl2.glMapBuffer(target, access); } @@ -1085,6 +1147,93 @@ public class PGL { } + public void drawTexture(int target, int id, int width, int height, + int texX0, int texY0, int texX1, int texY1, + int scrX0, int scrY0, int scrX1, int scrY1) { + + if (!loadedTexShader) { + texVertShader = createShader(GL_VERTEX_SHADER, texVertShaderSource); + texFragShader = createShader(GL_FRAGMENT_SHADER, texFragShaderSource); + if (0 < texVertShader && 0 < texFragShader) { + texShaderProgram = createProgram(texVertShader, texFragShader); + } + if (0 < texShaderProgram) { + texVertLoc = glGetAttribLocation(texShaderProgram, "inVertex"); + texTCoordLoc = glGetAttribLocation(texShaderProgram, "inTexcoord"); + } + texData = ByteBuffer.allocateDirect(texCoords.length * SIZEOF_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer(); + loadedTexShader = true; + } + + if (0 < texShaderProgram) { + // When drawing the texture we don't write to the + // depth mask, so the texture remains in the background + // and can be occluded by anything drawn later, even if + // if it is behind it. + boolean[] val = new boolean[1]; + glGetBooleanv(GL_DEPTH_WRITEMASK, val, 0); + boolean writeMask = val[0]; + glDepthMask(false); + + glUseProgram(texShaderProgram); + + glEnableVertexAttribArray(texVertLoc); + glEnableVertexAttribArray(texTCoordLoc); + + // Vertex coordinates of the textured quad are specified + // in normalized screen space (-1, 1): + + // Corner 1 + texCoords[ 0] = 2 * (float)scrX0 / pg.width - 1; + texCoords[ 1] = 2 * (float)scrY0 / pg.height - 1; + texCoords[ 2] = (float)texX0 / width; + texCoords[ 3] = (float)texY0 / height; + + // Corner 2 + texCoords[ 4] = 2 * (float)scrX1 / pg.width - 1; + texCoords[ 5] = 2 * (float)scrY0 / pg.height - 1; + texCoords[ 6] = (float)texX1 / width; + texCoords[ 7] = (float)texY0 / height; + + // Corner 3 + texCoords[ 8] = 2 * (float)scrX0 / pg.width - 1; + texCoords[ 9] = 2 * (float)scrY1 / pg.height - 1; + texCoords[10] = (float)texX0 / width; + texCoords[11] = (float)texY1 / height; + + // Corner 4 + texCoords[12] = 2 * (float)scrX1 / pg.width - 1; + texCoords[13] = 2 * (float)scrY1 / pg.height - 1; + texCoords[14] = (float)texX1 / width; + texCoords[15] = (float)texY1 / height; + + texData.rewind(); + texData.put(texCoords); + + enableTexturing(target); + glActiveTexture(target); + glBindTexture(target, id); + + texData.position(0); + glVertexAttribPointer(texVertLoc, 2, GL_FLOAT, false, 4 * SIZEOF_FLOAT, texData); + texData.position(2); + glVertexAttribPointer(texTCoordLoc, 2, GL_FLOAT, false, 4 * SIZEOF_FLOAT, texData); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glBindTexture(target, 0); + disableTexturing(target); + + glDisableVertexAttribArray(texVertLoc); + glDisableVertexAttribArray(texTCoordLoc); + + glUseProgram(0); + + glDepthMask(writeMask); + } + } + + // bit shifting this might be more efficient static public int nextPowerOfTwo(int val) { int ret = 1; @@ -1095,6 +1244,63 @@ public class PGL { } + public int createShader(int shaderType, String source) { + int shader = glCreateShader(shaderType); + if (shader != 0) { + glShaderSource(shader, source); + glCompileShader(shader); + int[] compiled = new int[1]; + glGetShaderiv(shader, GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == GL_FALSE) { + System.err.println("Could not compile shader " + shaderType + ":"); + System.err.println(glGetShaderInfoLog(shader)); + glDeleteShader(shader); + shader = 0; + } + } + return shader; + } + + + public int createProgram(int vertexShader, int fragmentShader) { + int program = glCreateProgram(); + if (program != 0) { + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + int[] linked = new int[1]; + glGetProgramiv(program, GL_LINK_STATUS, linked, 0); + if (linked[0] == GL_FALSE) { + System.err.println("Could not link program: "); + System.err.println(glGetProgramInfoLog(program)); + glDeleteProgram(program); + program = 0; + } + } + return program; + } + + + public boolean validateFramebuffer() { + int status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status == GL_FRAMEBUFFER_COMPLETE) { + return true; + } else if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT (" + Integer.toHexString(status) + ")"); + } else if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (" + Integer.toHexString(status) + ")"); + } else if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS (" + Integer.toHexString(status) + ")"); + } else if (status == GL_FRAMEBUFFER_INCOMPLETE_FORMATS) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_INCOMPLETE_FORMATS (" + Integer.toHexString(status) + ")"); + } else if (status == GL_FRAMEBUFFER_UNSUPPORTED) { + throw new RuntimeException("PFramebuffer: GL_FRAMEBUFFER_UNSUPPORTED" + Integer.toHexString(status)); + } else { + throw new RuntimeException("PFramebuffer: unknown framebuffer error (" + Integer.toHexString(status) + ")"); + } + } + + /////////////////////////////////////////////////////////////////////////////////// // Java specific stuff diff --git a/java/libraries/opengl/src/processing/opengl/PGraphicsOpenGL.java b/java/libraries/opengl/src/processing/opengl/PGraphicsOpenGL.java index 083ecf6de..be58f55e4 100644 --- a/java/libraries/opengl/src/processing/opengl/PGraphicsOpenGL.java +++ b/java/libraries/opengl/src/processing/opengl/PGraphicsOpenGL.java @@ -1507,17 +1507,17 @@ public class PGraphicsOpenGL extends PGraphics { pgl.glClearColor(0, 0, 0, 0); pgl.glClear(PGL.GL_DEPTH_BUFFER_BIT | PGL.GL_STENCIL_BUFFER_BIT); + if (primarySurface) { + pgl.beginOnscreenDraw(clearColorBuffer); + } else { + pgl.beginOffscreenDraw(clearColorBuffer); + } + if (hints[DISABLE_DEPTH_MASK]) { pgl.glDepthMask(false); } else { pgl.glDepthMask(true); - } - - if (primarySurface) { - pgl.beginOnscreenDraw(clearColorBuffer, parent.frameCount); - } else { - pgl.beginOffscreenDraw(clearColorBuffer, parent.frameCount); - } + } drawing = true; @@ -1548,9 +1548,9 @@ public class PGraphicsOpenGL extends PGraphics { // Restoring previous viewport. pgl.glViewport(savedViewport[0], savedViewport[1], savedViewport[2], savedViewport[3]); - if (primarySurface) { - pgl.glFlush(); + if (primarySurface) { pgl.endOnscreenDraw(clearColorBuffer0); + pgl.glFlush(); } else { if (offscreenMultisample) { offscreenFramebufferMultisample.copy(offscreenFramebuffer);