commit c5369f1bc82e265b3251e6ec7ac2670a4a58f544
Author: leszek <leszek@koltunski.pl>
Date:   Fri Feb 10 01:15:00 2017 +0000

    Major: change of API.
    
    Split DFramebuffer into Framebuffer and Screen; introduce the 'DistortedInputSurface' and 'DistortedOutputSurface' interfaces.

diff --git a/src/main/java/org/distorted/library/DistortedEffects.java b/src/main/java/org/distorted/library/DistortedEffects.java
index edf738b..8294912 100644
--- a/src/main/java/org/distorted/library/DistortedEffects.java
+++ b/src/main/java/org/distorted/library/DistortedEffects.java
@@ -183,7 +183,7 @@ public class DistortedEffects
 // DEBUG ONLY
 
   @SuppressWarnings("unused")
-  private void displayBoundingRect(float halfX, float halfY, float halfZ, DistortedFramebuffer df, float[] mvp, float[] vertices)
+  private void displayBoundingRect(float halfX, float halfY, float halfZ, DistortedOutputSurface surface, float[] mvp, float[] vertices)
     {
     int len  = vertices.length/3;
     int minx = Integer.MAX_VALUE;
@@ -194,6 +194,8 @@ public class DistortedEffects
 
     float x,y,z, X,Y,W, ndcX,ndcY;
 
+    DistortedProjection projection = surface.getProjection();
+
     for(int i=0; i<len; i++)
       {
       x = 2*halfX*vertices[3*i  ];
@@ -207,8 +209,8 @@ public class DistortedEffects
       ndcX = X/W;
       ndcY = Y/W;
 
-      wx = (int)(df.mWidth *(ndcX+1)/2);
-      wy = (int)(df.mHeight*(ndcY+1)/2);
+      wx = (int)(projection.mWidth *(ndcX+1)/2);
+      wy = (int)(projection.mHeight*(ndcY+1)/2);
 
       if( wx<minx ) minx = wx;
       if( wx>maxx ) maxx = wx;
@@ -217,13 +219,13 @@ public class DistortedEffects
       }
 
     mDebugProgram.useProgram();
-    df.setAsOutput();
+    surface.setAsOutput();
 
     Matrix.setIdentityM( mTmpMatrix, 0);
-    Matrix.translateM  ( mTmpMatrix, 0, minx-df.mWidth/2, maxy-df.mHeight/2, -df.mDistance);
+    Matrix.translateM  ( mTmpMatrix, 0, minx-projection.mWidth/2, maxy-projection.mHeight/2, -projection.mDistance);
     Matrix.scaleM      ( mTmpMatrix, 0, (float)(maxx-minx)/(2*halfX), (float)(maxy-miny)/(2*halfY), 1.0f);
     Matrix.translateM  ( mTmpMatrix, 0, halfX,-halfY, 0);
-    Matrix.multiplyMM  ( mMVPMatrix, 0, df.mProjectionMatrix, 0, mTmpMatrix, 0);
+    Matrix.multiplyMM  ( mMVPMatrix, 0, projection.mProjectionMatrix, 0, mTmpMatrix, 0);
 
     GLES30.glUniform2f( mObjDH , 2*halfX, 2*halfY);
     GLES30.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix , 0);
@@ -234,7 +236,7 @@ public class DistortedEffects
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void drawPriv(float halfInputW, float halfInputH, MeshObject mesh, DistortedFramebuffer df, long currTime)
+  void drawPriv(float halfInputW, float halfInputH, MeshObject mesh, DistortedOutputSurface surface, long currTime)
     {
     mM.compute(currTime);
     mV.compute(currTime);
@@ -242,13 +244,14 @@ public class DistortedEffects
     mP.compute(currTime);
 
     float halfZ = halfInputW*mesh.zFactor;
-    GLES30.glViewport(0, 0, df.mWidth, df.mHeight);
+    DistortedProjection projection = surface.getProjection();
+    GLES30.glViewport(0, 0, projection.mWidth, projection.mHeight);
 
     if( mP.mNumEffects==0 )
       {
       mProgram.useProgram();
-      df.setAsOutput();
-      mM.send(df,halfInputW,halfInputH,halfZ);
+      surface.setAsOutput();
+      mM.send(projection,halfInputW,halfInputH,halfZ);
       mV.send(halfInputW,halfInputH,halfZ);
       mF.send(halfInputW,halfInputH);
       GLES30.glVertexAttribPointer(mProgram.mAttribute[0], POSITION_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mesh.mMeshPositions);
@@ -260,17 +263,17 @@ public class DistortedEffects
       {
       if( mV.mNumEffects==0 && mF.mNumEffects==0 && (mesh instanceof MeshFlat) && mM.canUseShortcut() )
         {
-        mM.constructMatrices(df,halfInputW,halfInputH);
-        mP.render(2*halfInputW, 2*halfInputH, mM.getMVP(), df);
+        mM.constructMatrices(projection,halfInputW,halfInputH);
+        mP.render(2*halfInputW, 2*halfInputH, mM.getMVP(), surface);
         }
       else
         {
         mProgram.useProgram();
-        mBufferFBO.resizeFast(df.mWidth, df.mHeight);
+        mBufferFBO.resizeFast(projection.mWidth, projection.mHeight);
         mBufferFBO.setAsOutput();
         GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
         GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
-        mM.send(mBufferFBO,halfInputW,halfInputH,halfZ);
+        mM.send(mBufferFBO.getProjection(),halfInputW,halfInputH,halfZ);
         mV.send(halfInputW,halfInputH,halfZ);
         mF.send(halfInputW,halfInputH);
         GLES30.glVertexAttribPointer(mProgram.mAttribute[0], POSITION_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mesh.mMeshPositions);
@@ -279,11 +282,11 @@ public class DistortedEffects
         GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mesh.dataLength);
 
         Matrix.setIdentityM(mTmpMatrix, 0);
-        Matrix.translateM(mTmpMatrix, 0, 0, 0, -df.mDistance);
-        Matrix.multiplyMM(mMVPMatrix, 0, df.mProjectionMatrix, 0, mTmpMatrix, 0);
+        Matrix.translateM(mTmpMatrix, 0, 0, 0, -projection.mDistance);
+        Matrix.multiplyMM(mMVPMatrix, 0, projection.mProjectionMatrix, 0, mTmpMatrix, 0);
 
         mBufferFBO.setAsInput();
-        mP.render(df.mWidth, df.mHeight, mMVPMatrix, df);
+        mP.render(projection.mWidth, projection.mHeight, mMVPMatrix, surface);
         }
       }
 
@@ -294,15 +297,15 @@ public class DistortedEffects
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
    
-  static void drawNoEffectsPriv(float halfInputW, float halfInputH, MeshObject mesh, DistortedFramebuffer df)
+  static void drawNoEffectsPriv(float halfInputW, float halfInputH, MeshObject mesh, DistortedProjection projection)
     {
-    GLES30.glViewport(0, 0, df.mWidth, df.mHeight);
+    GLES30.glViewport(0, 0, projection.mWidth, projection.mHeight);
 
     Matrix.setIdentityM(mTmpMatrix, 0);
-    Matrix.translateM(mTmpMatrix, 0, 0, 0, -df.mDistance);
-    Matrix.multiplyMM(mMVPMatrix, 0, df.mProjectionMatrix, 0, mTmpMatrix, 0);
+    Matrix.translateM(mTmpMatrix, 0, 0, 0, -projection.mDistance);
+    Matrix.multiplyMM(mMVPMatrix, 0, projection.mProjectionMatrix, 0, mTmpMatrix, 0);
 
-    EffectQueueMatrix.sendZero(df,halfInputW,halfInputH,halfInputW*mesh.zFactor);
+    EffectQueueMatrix.sendZero(projection,halfInputW,halfInputH,halfInputW*mesh.zFactor);
     EffectQueueVertex.sendZero();
     EffectQueueFragment.sendZero();
 
diff --git a/src/main/java/org/distorted/library/DistortedFramebuffer.java b/src/main/java/org/distorted/library/DistortedFramebuffer.java
index cbf693e..083c4c4 100644
--- a/src/main/java/org/distorted/library/DistortedFramebuffer.java
+++ b/src/main/java/org/distorted/library/DistortedFramebuffer.java
@@ -20,40 +20,26 @@
 package org.distorted.library;
 
 import android.opengl.GLES30;
-import android.opengl.Matrix;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Class which represents a OpenGL Framebuffer object.
  * <p>
- * User is able to create either Framebuffers from objects already constructed outside
- * of the library (the first constructor; primary use case: the screen) or an offscreen
- * FBOs.
- * <p>
- * Keep all objects created in a static LinkedList. The point: we need to be able to mark
- * Framebuffers for deletion, and delete all marked objects later at a convenient time (that's
- * because we can only delete from a thread that holds the OpenGL context so here we provide a
- * framework where one is able to mark for deletion at any time and actual deletion takes place
- * on the next render).
+ * User is able to create offscreen FBOs and both a) render to them b) use their COLOR0 attachment as
+ * an input texture.
  */
-public class DistortedFramebuffer extends DistortedRenderable
+public class DistortedFramebuffer extends DistortedRenderable implements DistortedInputSurface, DistortedOutputSurface
   {
   private int[] mDepthH = new int[1];
   private int[] mFBOH   = new int[1];
-
   private boolean mDepthEnabled;
-
-  // Projection stuff
-  private float mX, mY, mFOV;
-  int mWidth,mHeight,mDepth;
-  float mDistance;
-  float[] mProjectionMatrix;
+  private DistortedProjection mProjection;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Must be called from a thread holding OpenGL Context
 // Watch out - this has the side-effect of binding a Texture and a Framebuffer!
 
-  void create()
+  public void create()
     {
     if( mColorH[0]==NOT_CREATED_YET )
       {
@@ -146,59 +132,6 @@ public class DistortedFramebuffer extends DistortedRenderable
     if( mDepthEnabled           ) mDepthH[0] = NOT_CREATED_YET;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setAsOutput()
-    {
-    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[0]);
-
-    if( mDepthH[0]!=NOT_CREATED_YET )
-      {
-      GLES30.glEnable(GLES30.GL_DEPTH_TEST);
-      GLES30.glDepthMask(true);
-      }
-    else
-      {
-      GLES30.glDisable(GLES30.GL_DEPTH_TEST);
-      GLES30.glDepthMask(false);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createProjection()
-    {
-    if( mWidth>0 && mHeight>1 )
-      {
-      if( mFOV>0.0f )  // perspective projection
-        {
-        float left   = (-mX-mWidth /2.0f)/mHeight;
-        float right  = (-mX+mWidth /2.0f)/mHeight;
-        float bottom = (-mY-mHeight/2.0f)/mHeight;
-        float top    = (-mY+mHeight/2.0f)/mHeight;
-        float near   = (top-bottom) / (2.0f*(float)Math.tan(mFOV*Math.PI/360));
-        mDistance    = mHeight*near/(top-bottom);
-        float far    = 2*mDistance-near;
-        mDepth       = (int)((far-near)/2);
-
-        Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
-        }
-      else             // parallel projection
-        {
-        float left   = -mX-mWidth /2.0f;
-        float right  = -mX+mWidth /2.0f;
-        float bottom = -mY-mHeight/2.0f;
-        float top    = -mY+mHeight/2.0f;
-        float near   = (mWidth+mHeight)/2;
-        mDistance    = 2*near;
-        float far    = 3*near;
-        mDepth       = (int)near;
-
-        Matrix.orthoM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
-        }
-      }
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // if new size fits into the size of the underlying Texture, just change the projection without
 // reallocating the Texture. Otherwise, we need to reallocate.
@@ -207,17 +140,12 @@ public class DistortedFramebuffer extends DistortedRenderable
 
   void resizeFast(int width, int height)
     {
-    if( mWidth!=width || mHeight!=height )
+    if( mProjection.resize(width,height) )
       {
-      mWidth = width;
-      mHeight= height;
-
-      createProjection();
-
-      if( mWidth> mSizeX || mHeight> mSizeY)
+      if( width> mSizeX || height> mSizeY)
         {
-        mSizeX = mWidth;
-        mSizeY = mHeight;
+        mSizeX = width;
+        mSizeY = height;
         delete();
         }
       }
@@ -240,18 +168,10 @@ public class DistortedFramebuffer extends DistortedRenderable
     {
     super(width,height,NOT_CREATED_YET);
 
-    mProjectionMatrix = new float[16];
-
-    mHeight      = height;
-    mWidth       = width;
-    mFOV         = 60.0f;
-    mX           = 0.0f;
-    mY           = 0.0f;
+    mProjection  = new DistortedProjection(width,height);
     mDepthEnabled= depthEnabled;
     mFBOH[0]     = NOT_CREATED_YET;
     mDepthH[0]   = NOT_CREATED_YET;
-
-    createProjection();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -267,40 +187,45 @@ public class DistortedFramebuffer extends DistortedRenderable
     {
     super(width,height,NOT_CREATED_YET);
 
-    mProjectionMatrix = new float[16];
-
-    mHeight      = height;
-    mWidth       = width;
-    mFOV         = 60.0f;
-    mX           = 0.0f;
-    mY           = 0.0f;
+    mProjection  = new DistortedProjection(width,height);
     mDepthEnabled= false;
     mFBOH[0]     = NOT_CREATED_YET;
     mDepthH[0]   = NOT_CREATED_YET;
-
-    createProjection();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Create a new Framebuffer from an already created OpenGL Framebuffer.
- * <p>
- * Has to be followed by a 'resize()' to set the size.
- *
- * @param fbo the ID of a OpenGL Framebuffer object. Typically 0 (the screen)
+ * Bind the underlying rectangle of pixels as a OpenGL Texture.
  */
-  public DistortedFramebuffer(int fbo)
+  public boolean setAsInput()
     {
-    super(0,0,DONT_CREATE);
+    if( mColorH[0]>0 )
+      {
+      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[0]);
+      return true;
+      }
+
+    return false;
+    }
 
-    mProjectionMatrix = new float[16];
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Bind this Surface as a Framebuffer we can render to.
+ */
+  public void setAsOutput()
+    {
+    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[0]);
 
-    mFOV         = 60.0f;
-    mX           = 0.0f;
-    mY           = 0.0f;
-    mDepthEnabled= true;
-    mFBOH[0]     = fbo;
-    mDepthH[0]   = DONT_CREATE;
+    if( mDepthH[0]!=NOT_CREATED_YET )
+      {
+      GLES30.glEnable(GLES30.GL_DEPTH_TEST);
+      GLES30.glDepthMask(true);
+      }
+    else
+      {
+      GLES30.glDisable(GLES30.GL_DEPTH_TEST);
+      GLES30.glDepthMask(false);
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -322,20 +247,20 @@ public class DistortedFramebuffer extends DistortedRenderable
  * <p>
  * Must be called from a thread holding OpenGL Context.
  *
- * @param ren input Renderable to use.
+ * @param surface InputSurface to skin our Mesh with.
  * @param mesh Class descendant from MeshObject
  * @param effects The DistortedEffects to use when rendering
  * @param time Current time, in milliseconds.
  */
-  public void renderTo(DistortedRenderable ren, MeshObject mesh, DistortedEffects effects, long time)
+  public void renderTo(DistortedInputSurface surface, MeshObject mesh, DistortedEffects effects, long time)
     {
-    ren.create();  // Watch out  - this needs to be before
-    create();      // the 'setAsInput' because this has side-effects!
+    surface.create();  // Watch out  - this needs to be before
+    create();          // the 'setAsInput' because this has side-effects!
 
-    if( ren.setAsInput() )
+    if( surface.setAsInput() )
       {
       DistortedRenderable.deleteAllMarked();
-      effects.drawPriv(ren.getWidth()/2.0f, ren.getHeight()/2.0f, mesh, this, time);
+      effects.drawPriv(surface.getWidth()/2.0f, surface.getHeight()/2.0f, mesh, this, time);
       }
     }
 
@@ -365,38 +290,23 @@ public class DistortedFramebuffer extends DistortedRenderable
  */
   public void setProjection(float fov, float x, float y)
     {
-    mFOV = fov;
-    mX   = x;
-    mY   = y;
-
-    createProjection();
+    mProjection.set(fov,x,y);
+    mProjection.createProjection();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Resize the underlying Framebuffer.
  *
- * As the Framebuffer is not created until the first render, typical usage of this API is actually
- * to set the size of an not-yet-created Framebuffer of an object that has been created with the
- * second constructor.
- * <p>
- * Fully creating an object, rendering to it, then resizing mid-render is also possible. Actual
- * resize takes place on the next render.
- *
  * @param width The new width.
  * @param height The new height.
  */
   public void resize(int width, int height)
     {
-    if( mWidth!=width || mHeight!=height )
+    if( mProjection.resize(width,height) )
       {
-      mWidth = width;
-      mHeight= height;
       mSizeX = width;
       mSizeY = height;
-
-      createProjection();
-
       if( mColorH[0]>0 ) markForDeletion();
       }
     }
@@ -427,4 +337,15 @@ public class DistortedFramebuffer extends DistortedRenderable
     {
     return mDepthEnabled;
     }
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return Projection stored in this Framebuffer.
+ *
+ * @return DistortedProjection object.
+ */
+  public DistortedProjection getProjection()
+    {
+    return mProjection;
+    }
   }
diff --git a/src/main/java/org/distorted/library/DistortedInputSurface.java b/src/main/java/org/distorted/library/DistortedInputSurface.java
new file mode 100644
index 0000000..10ec49f
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedInputSurface.java
@@ -0,0 +1,34 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted 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 General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A Surface that we can set as Input, i.e. take its rectangle of pixels and skin our Mesh with it.
+ */
+
+public interface DistortedInputSurface extends DistortedSurface
+{
+/**
+ * Take the underlying rectangle of pixels and bind this texture to OpenGL.
+ */
+  boolean setAsInput();
+}
diff --git a/src/main/java/org/distorted/library/DistortedOutputSurface.java b/src/main/java/org/distorted/library/DistortedOutputSurface.java
new file mode 100644
index 0000000..51aa36d
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedOutputSurface.java
@@ -0,0 +1,38 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted 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 General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A Surface that we can set as Output, i.e. render to it.
+ */
+
+public interface DistortedOutputSurface extends DistortedSurface
+{
+/**
+ * Bind this Surface as a Framebuffer we can render to.
+ */
+ void setAsOutput();
+/**
+ * Return the Projection.
+ */
+ DistortedProjection getProjection();
+}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/DistortedProjection.java b/src/main/java/org/distorted/library/DistortedProjection.java
new file mode 100644
index 0000000..2e0e856
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedProjection.java
@@ -0,0 +1,124 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted 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 General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library;
+
+import android.opengl.Matrix;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class DistortedProjection
+{
+  private float mX, mY, mFOV;
+  int mWidth,mHeight,mDepth;
+  float mDistance;
+  float[] mProjectionMatrix;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedProjection()
+    {
+    mProjectionMatrix = new float[16];
+
+    mWidth = 0;
+    mHeight= 0;
+
+    mFOV = 60.0f;
+    mX   =  0.0f;
+    mY   =  0.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedProjection(int width, int height)
+    {
+    mProjectionMatrix = new float[16];
+
+    mWidth = width;
+    mHeight= height;
+
+    mFOV = 60.0f;
+    mX   =  0.0f;
+    mY   =  0.0f;
+
+    createProjection();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean resize(int width, int height)
+    {
+    if( mWidth!=width || mHeight!=height )
+      {
+      mWidth = width;
+      mHeight= height;
+      createProjection();
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void set(float fov, float x, float y)
+    {
+    mFOV = fov;
+    mX = x;
+    mY = y;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void createProjection()
+    {
+    if( mWidth>0 && mHeight>1 )
+      {
+      if( mFOV>0.0f )  // perspective projection
+        {
+        float left   = (-mX-mWidth /2.0f)/mHeight;
+        float right  = (-mX+mWidth /2.0f)/mHeight;
+        float bottom = (-mY-mHeight/2.0f)/mHeight;
+        float top    = (-mY+mHeight/2.0f)/mHeight;
+        float near   = (top-bottom) / (2.0f*(float)Math.tan(mFOV*Math.PI/360));
+        mDistance    = mHeight*near/(top-bottom);
+        float far    = 2*mDistance-near;
+        mDepth       = (int)((far-near)/2);
+
+        Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
+        }
+      else             // parallel projection
+        {
+        float left   = -mX-mWidth /2.0f;
+        float right  = -mX+mWidth /2.0f;
+        float bottom = -mY-mHeight/2.0f;
+        float top    = -mY+mHeight/2.0f;
+        float near   = (mWidth+mHeight)/2;
+        mDistance    = 2*near;
+        float far    = 3*near;
+        mDepth       = (int)near;
+
+        Matrix.orthoM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+}
diff --git a/src/main/java/org/distorted/library/DistortedRenderable.java b/src/main/java/org/distorted/library/DistortedRenderable.java
index 15d6623..0209336 100644
--- a/src/main/java/org/distorted/library/DistortedRenderable.java
+++ b/src/main/java/org/distorted/library/DistortedRenderable.java
@@ -19,18 +19,18 @@
 
 package org.distorted.library;
 
-import android.opengl.GLES30;
-
 import java.util.Iterator;
 import java.util.LinkedList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Abstract class which represents a Renderable Object, i.e. something that we can take an skin our Mesh with.
- * <p>
- * Currently a DistortedTexture or a DistortedFramebuffer are Renderables.
- */
-public abstract class DistortedRenderable
+ * Keep all objects created in a static LinkedList. The point: we need to be able to mark
+ * Objects for deletion, and delete all marked Objects later at a convenient time (that's
+ * because we can only delete from a thread that holds the OpenGL context so here we provide a
+ * framework where one is able to mark for deletion at any time and actual deletion takes place
+ * on the next render).
+*/
+abstract class DistortedRenderable implements DistortedSurface
   {
   static final int FAILED_TO_CREATE = -1;
   static final int NOT_CREATED_YET  = -2;
@@ -45,7 +45,7 @@ public abstract class DistortedRenderable
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  abstract void create();
+  public abstract void create();
   abstract void delete();
   abstract void destroy();
 
@@ -75,26 +75,6 @@ public abstract class DistortedRenderable
       }
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  long getID()
-    {
-    return mColorH[0];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  boolean setAsInput()
-    {
-    if( mColorH[0]>0 )
-      {
-      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[0]);
-      return true;
-      }
-
-    return false;
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   static synchronized void onDestroy()
@@ -131,6 +111,15 @@ public abstract class DistortedRenderable
     mMarked     = true;
     }
 
+////////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return unique ID of the Renderable.
+ */
+  public long getID()
+    {
+    return mColorH[0];
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 /**
diff --git a/src/main/java/org/distorted/library/DistortedScreen.java b/src/main/java/org/distorted/library/DistortedScreen.java
new file mode 100644
index 0000000..d672ceb
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedScreen.java
@@ -0,0 +1,148 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted 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 General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library;
+
+import android.opengl.GLES30;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Class which represents the Screen.
+ * <p>
+ * User is able to render to it just like to a DistortedFramebuffer.
+ */
+public class DistortedScreen extends DistortedRenderable implements DistortedOutputSurface
+  {
+  private int[] mFBOH   = new int[1];
+  private DistortedProjection mProjection;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void create() {}
+  void delete() {}
+  void destroy() {}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a new Screen.
+ * <p>
+ * Has to be followed by a 'resize()' to set the size.
+ */
+  public DistortedScreen()
+    {
+    super(0,0,DONT_CREATE);
+
+    mProjection  = new DistortedProjection();
+    mFBOH[0]     = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Bind this Surface as a Framebuffer we can render to.
+ */
+  public void setAsOutput()
+    {
+    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[0]);
+    GLES30.glEnable(GLES30.GL_DEPTH_TEST);
+    GLES30.glDepthMask(true);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Draw the (texture,mesh,effects) object to the Framebuffer.
+ * <p>
+ * Must be called from a thread holding OpenGL Context.
+ *
+ * @param surface InputSurface to skin our Mesh with.
+ * @param mesh Class descendant from MeshObject
+ * @param effects The DistortedEffects to use when rendering
+ * @param time Current time, in milliseconds.
+ */
+  public void renderTo(DistortedInputSurface surface, MeshObject mesh, DistortedEffects effects, long time)
+    {
+    surface.create();  // Watch out  - this needs to be before
+    create();          // the 'setAsInput' because this has side-effects!
+
+    if( surface.setAsInput() )
+      {
+      DistortedRenderable.deleteAllMarked();
+      effects.drawPriv(surface.getWidth()/2.0f, surface.getHeight()/2.0f, mesh, this, time);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Draws the Tree, and all its children, to the Framebuffer.
+ * <p>
+ * Must be called from a thread holding OpenGL Context.
+ *
+ * @param dt DistortedTree to render.
+ * @param time Current time, in milliseconds. This will be passed to all the Effects stored in the Tree.
+ */
+  public void renderTo(DistortedTree dt, long time)
+    {
+    DistortedRenderable.deleteAllMarked();
+    create();
+    dt.drawRecursive(time,this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create new Projection matrix.
+ *
+ * @param fov Vertical 'field of view' of the Projection frustrum (in degrees).
+ * @param x X-coordinate of the point at which our camera looks at. 0 is the center.
+ * @param y Y-coordinate of the point at which our camera looks at. 0 is the center.
+ */
+  public void setProjection(float fov, float x, float y)
+    {
+    mProjection.set(fov,x,y);
+    mProjection.createProjection();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Resize the underlying Projection.
+ *
+ * @param width The new width.
+ * @param height The new height.
+ */
+  public void resize(int width, int height)
+    {
+    if( mProjection.resize(width,height) )
+      {
+      mSizeX = width;
+      mSizeY = height;
+      }
+    }
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return Projection stored in this Framebuffer.
+ *
+ * @return DistortedProjection object.
+ */
+  public DistortedProjection getProjection()
+    {
+    return mProjection;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/DistortedSurface.java b/src/main/java/org/distorted/library/DistortedSurface.java
new file mode 100644
index 0000000..dc02eb4
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedSurface.java
@@ -0,0 +1,46 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted 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 General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Abstract Surface.
+ */
+
+interface DistortedSurface
+{
+/**
+ * Create the underlying OpenGL part of the Surface.
+ */
+  void create();
+/**
+ * Return a unique ID of this Surface.
+ */
+  long getID();
+/**
+ * Return the width of this Surface.
+ */
+  int getWidth();
+/**
+ * Return the height of this Surface.
+ */
+  int getHeight();
+}
diff --git a/src/main/java/org/distorted/library/DistortedTexture.java b/src/main/java/org/distorted/library/DistortedTexture.java
index 79d0596..67799ff 100644
--- a/src/main/java/org/distorted/library/DistortedTexture.java
+++ b/src/main/java/org/distorted/library/DistortedTexture.java
@@ -29,14 +29,8 @@ import android.opengl.GLUtils;
  * Class which represents a OpenGL Texture object.
  * <p>
  * Create a Texture of arbitrary size and feed some pixels to it via the setTexture() method.
- * <p>
- * Keep all objects created in a static LinkedList. The point: we need to be able to mark
- * Textures for deletion, and delete all marked objects later at a convenient time (that's
- * because we can only delete from a thread that holds the OpenGL context so here we provide a
- * framework where one is able to mark for deletion at any time and actual deletion takes place
- * on the next render).
  */
-public class DistortedTexture extends DistortedRenderable
+public class DistortedTexture extends DistortedRenderable implements DistortedInputSurface
   {
   private Bitmap mBmp= null;
 
@@ -61,7 +55,7 @@ public class DistortedTexture extends DistortedRenderable
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // must be called from a thread holding OpenGL Context
 
-  void create()
+  public void create()
     {
     if( mBmp!=null && mColorH !=null )
       {
@@ -104,6 +98,7 @@ public class DistortedTexture extends DistortedRenderable
     {
     if( mColorH[0]!=DONT_CREATE ) mColorH[0] = NOT_CREATED_YET;
     }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   static void getUniforms(int mProgramH)
@@ -126,6 +121,21 @@ public class DistortedTexture extends DistortedRenderable
     mBmp= null;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Bind the underlying rectangle of pixels as a OpenGL Texture.
+ */
+  public boolean setAsInput()
+    {
+    if( mColorH[0]>0 )
+      {
+      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[0]);
+      return true;
+      }
+
+    return false;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Sets the underlying android.graphics.Bitmap object.
diff --git a/src/main/java/org/distorted/library/DistortedTree.java b/src/main/java/org/distorted/library/DistortedTree.java
index 47cf2f6..8ce9e71 100644
--- a/src/main/java/org/distorted/library/DistortedTree.java
+++ b/src/main/java/org/distorted/library/DistortedTree.java
@@ -38,7 +38,7 @@ public class DistortedTree
 
   private MeshObject mMesh;
   private DistortedEffects mEffects;
-  private DistortedRenderable mRenderable;
+  private DistortedInputSurface mSurface;
   private NodeData mData;
 
   private DistortedTree mParent;
@@ -94,7 +94,7 @@ public class DistortedTree
     {
     ArrayList<Long> ret = new ArrayList<>();
      
-    ret.add( mRenderable.getID() );
+    ret.add( mSurface.getID() );
     DistortedTree node;
    
     for(int i=0; i<mNumChildren[0]; i++)
@@ -125,7 +125,7 @@ public class DistortedTree
       if( newList.size()>1 )
         {
         if( mData.mFBO ==null )
-          mData.mFBO = new DistortedFramebuffer(mRenderable.getWidth(), mRenderable.getHeight());
+          mData.mFBO = new DistortedFramebuffer(mSurface.getWidth(), mSurface.getHeight());
         }
       else
         {
@@ -198,15 +198,15 @@ public class DistortedTree
 */
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void drawRecursive(long currTime, DistortedFramebuffer df)
+  void drawRecursive(long currTime, DistortedOutputSurface surface)
     {
-    mRenderable.create();
-    float halfX = mRenderable.getWidth()/2.0f;
-    float halfY = mRenderable.getHeight()/2.0f;
+    mSurface.create();
+    float halfX = mSurface.getWidth()/2.0f;
+    float halfY = mSurface.getHeight()/2.0f;
 
     if( mNumChildren[0]<=0 )
       {
-      mRenderable.setAsInput();
+      mSurface.setAsInput();
       }
     else
       {
@@ -219,8 +219,8 @@ public class DistortedTree
         GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
         GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
 
-        if( mRenderable.setAsInput() )
-          DistortedEffects.drawNoEffectsPriv(halfX, halfY, mMesh, mData.mFBO);
+        if( mSurface.setAsInput() )
+          DistortedEffects.drawNoEffectsPriv(halfX, halfY, mMesh, mData.mFBO.getProjection() );
 
         synchronized(this)
           {
@@ -236,7 +236,7 @@ public class DistortedTree
       mData.mFBO.setAsInput();
       }
 
-    mEffects.drawPriv(halfX, halfY, mMesh, df, currTime);
+    mEffects.drawPriv(halfX, halfY, mMesh, surface, currTime);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -245,13 +245,13 @@ public class DistortedTree
 /**
  * Constructs new Node of the Tree.
  *     
- * @param renderable DistortedRenderable to put into the new Node.
+ * @param surface InputSurface to put into the new Node.
  * @param effects DistortedEffects to put into the new Node.
  * @param mesh MeshObject to put into the new Node.
  */
-  public DistortedTree(DistortedRenderable renderable, DistortedEffects effects, MeshObject mesh)
+  public DistortedTree(DistortedInputSurface surface, DistortedEffects effects, MeshObject mesh)
     {
-    mRenderable    = renderable;
+    mSurface       = surface;
     mEffects       = effects;
     mMesh          = mesh;
     mParent        = null;
@@ -260,7 +260,7 @@ public class DistortedTree
     mNumChildren[0]= 0;
    
     ArrayList<Long> list = new ArrayList<>();
-    list.add(mRenderable.getID());
+    list.add(mSurface.getID());
 
     mData = mMapNodeID.get(list);
    
@@ -293,21 +293,21 @@ public class DistortedTree
 
     if( (flags & Distorted.CLONE_RENDERABLE) != 0 )
       {
-      mRenderable = node.mRenderable;
+      mSurface = node.mSurface;
       }
     else
       {
-      int w = node.mRenderable.getWidth();
-      int h = node.mRenderable.getHeight();
+      int w = node.mSurface.getWidth();
+      int h = node.mSurface.getHeight();
 
-      if( node.mRenderable instanceof DistortedTexture )
+      if( node.mSurface instanceof DistortedTexture )
         {
-        mRenderable = new DistortedTexture(w,h);
+        mSurface = new DistortedTexture(w,h);
         }
-      else if( node.mRenderable instanceof DistortedFramebuffer )
+      else if( node.mSurface instanceof DistortedFramebuffer )
         {
-        boolean hasDepth = ((DistortedFramebuffer) node.mRenderable).hasDepth();
-        mRenderable = new DistortedFramebuffer(w,h,hasDepth);
+        boolean hasDepth = ((DistortedFramebuffer) node.mSurface).hasDepth();
+        mSurface = new DistortedFramebuffer(w,h,hasDepth);
         }
       }
     if( (flags & Distorted.CLONE_CHILDREN) != 0 )
@@ -360,17 +360,17 @@ public class DistortedTree
 /**
  * Adds a new child to the last position in the list of our Node's children.
  * 
- * @param renderable DistortedRenderable to initialize our child Node with.
+ * @param surface InputSurface to initialize our child Node with.
  * @param effects DistortedEffects to initialize our child Node with.
  * @param mesh MeshObject to initialize our child Node with.
  * @return the newly constructed child Node, or null if we couldn't allocate resources.
  */
-  public synchronized DistortedTree attach(DistortedRenderable renderable, DistortedEffects effects, MeshObject mesh)
+  public synchronized DistortedTree attach(DistortedInputSurface surface, DistortedEffects effects, MeshObject mesh)
     {
     ArrayList<Long> prev = generateIDList(); 
       
     if( mChildren==null ) mChildren = new ArrayList<>(2);
-    DistortedTree node = new DistortedTree(renderable,effects,mesh);
+    DistortedTree node = new DistortedTree(surface,effects,mesh);
     node.mParent = this;
     mChildren.add(node);
     mNumChildren[0]++;
@@ -474,13 +474,13 @@ public class DistortedTree
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Returns the DistortedRenderable object that's in the Node.
+ * Returns the DistortedInputSurface object that's in the Node.
  *
- * @return The DistortedRenderable contained in the Node.
+ * @return The DistortedInputSurface contained in the Node.
  */
-  public DistortedRenderable getRenderable()
+  public DistortedInputSurface getSurface()
     {
-    return mRenderable;
+    return mSurface;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/EffectQueueMatrix.java b/src/main/java/org/distorted/library/EffectQueueMatrix.java
index 8370438..1219d0b 100644
--- a/src/main/java/org/distorted/library/EffectQueueMatrix.java
+++ b/src/main/java/org/distorted/library/EffectQueueMatrix.java
@@ -107,10 +107,10 @@ class EffectQueueMatrix extends EffectQueue
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // here construct the ModelView and the ModelViewProjection Matrices
 
-  void constructMatrices(DistortedFramebuffer df, float halfX, float halfY)
+  void constructMatrices(DistortedProjection projection, float halfX, float halfY)
     {
     Matrix.setIdentityM(mViewMatrix, 0);
-    Matrix.translateM(mViewMatrix, 0, -df.mWidth/2, df.mHeight/2, -df.mDistance);
+    Matrix.translateM(mViewMatrix, 0, -projection.mWidth/2, projection.mHeight/2, -projection.mDistance);
 
     float x,y,z, sx,sy,sz;
 
@@ -184,7 +184,7 @@ class EffectQueueMatrix extends EffectQueue
       }
 
     Matrix.translateM(mViewMatrix, 0, halfX,-halfY, 0);
-    Matrix.multiplyMM(mMVPMatrix, 0, df.mProjectionMatrix, 0, mViewMatrix, 0);
+    Matrix.multiplyMM(mMVPMatrix, 0, projection.mProjectionMatrix, 0, mViewMatrix, 0);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -284,12 +284,12 @@ class EffectQueueMatrix extends EffectQueue
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  synchronized void send(DistortedFramebuffer df, float halfX, float halfY, float halfZ)
+  synchronized void send(DistortedProjection projection, float halfX, float halfY, float halfZ)
     {
-    constructMatrices(df,halfX,halfY);
+    constructMatrices(projection,halfX,halfY);
 
     GLES30.glUniform3f( mObjDH , halfX, halfY, halfZ);
-    GLES30.glUniform1f( mDepthH, df.mDepth);
+    GLES30.glUniform1f( mDepthH, projection.mDepth);
     GLES30.glUniformMatrix4fv(mMVMatrixH , 1, false, mViewMatrix, 0);
     GLES30.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix , 0);
     }
@@ -297,14 +297,14 @@ class EffectQueueMatrix extends EffectQueue
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // here construct the ModelView Matrix, but without any effects
 
-  synchronized static void sendZero(DistortedFramebuffer df, float halfX, float halfY, float halfZ)
+  synchronized static void sendZero(DistortedProjection projection, float halfX, float halfY, float halfZ)
     {
     Matrix.setIdentityM(mTmpMatrix, 0);
-    Matrix.translateM(mTmpMatrix, 0, halfX-df.mWidth/2, df.mHeight/2-halfY, -df.mDistance);
-    Matrix.multiplyMM(mMVPMatrix, 0, df.mProjectionMatrix, 0, mTmpMatrix, 0);
+    Matrix.translateM(mTmpMatrix, 0, halfX-projection.mWidth/2, projection.mHeight/2-halfY, -projection.mDistance);
+    Matrix.multiplyMM(mMVPMatrix, 0, projection.mProjectionMatrix, 0, mTmpMatrix, 0);
     
     GLES30.glUniform3f( mObjDH , halfX, halfY, halfZ);
-    GLES30.glUniform1f( mDepthH, df.mDepth);
+    GLES30.glUniform1f( mDepthH, projection.mDepth);
     GLES30.glUniformMatrix4fv(mMVMatrixH , 1, false, mTmpMatrix, 0);
     GLES30.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
     }
diff --git a/src/main/java/org/distorted/library/EffectQueuePostprocess.java b/src/main/java/org/distorted/library/EffectQueuePostprocess.java
index 368113b..f33ec10 100644
--- a/src/main/java/org/distorted/library/EffectQueuePostprocess.java
+++ b/src/main/java/org/distorted/library/EffectQueuePostprocess.java
@@ -229,10 +229,13 @@ class EffectQueuePostprocess extends EffectQueue
 // w,h - width and height of the input texture. MVP - Model-View-Projection matrix to apply to the
 // texture; df - output FBO.
 
-  synchronized void render(float w, float h, float[] mvp, DistortedFramebuffer df)
+  synchronized void render(float w, float h, float[] mvp, DistortedOutputSurface surface)
     {
     mBlurProgram.useProgram();
 
+    DistortedProjection inputP  = mBufferFBO.getProjection();
+    DistortedProjection outputP = surface.getProjection();
+
     int radius = (int)mUniforms[0];
     if( radius>=MAX_BLUR ) radius = MAX_BLUR-1;
     computeGaussianKernel(radius);
@@ -252,8 +255,8 @@ class EffectQueuePostprocess extends EffectQueue
     GLES30.glViewport(0, 0, (int)w, (int)h);
 
     Matrix.setIdentityM(mTmpMatrix, 0);
-    Matrix.translateM(mTmpMatrix, 0, 0, 0, -mBufferFBO.mDistance);
-    Matrix.multiplyMM(mMVPMatrix, 0, mBufferFBO.mProjectionMatrix, 0, mTmpMatrix, 0);
+    Matrix.translateM(mTmpMatrix, 0, 0, 0, -inputP.mDistance);
+    Matrix.multiplyMM(mMVPMatrix, 0, inputP.mProjectionMatrix, 0, mTmpMatrix, 0);
 
     // horizontal blur
     GLES30.glUniform1fv( mOffsetsH ,radius+1, mOffsets,0);
@@ -263,8 +266,9 @@ class EffectQueuePostprocess extends EffectQueue
     GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
 
     mBufferFBO.setAsInput();
-    df.setAsOutput();
-    GLES30.glViewport(0, 0, df.mWidth, df.mHeight);
+    surface.setAsOutput();
+
+    GLES30.glViewport(0, 0, outputP.mWidth, outputP.mHeight);
 
     for(int i=0; i<=radius; i++) mOffsets[i] = offsetsCache[offset+i]/w;
 
