commit af4cc5db958df58d1ea5f277ff8bf62396d86021
Author: Leszek Koltunski <leszek@distoretedandroid.org>
Date:   Fri Feb 10 11:07:07 2017 +0000

    Simplify yesterday's refactoring.

diff --git a/src/main/java/org/distorted/library/Distorted.java b/src/main/java/org/distorted/library/Distorted.java
index c488fb9..5c619a4 100644
--- a/src/main/java/org/distorted/library/Distorted.java
+++ b/src/main/java/org/distorted/library/Distorted.java
@@ -24,8 +24,6 @@ import android.content.res.Resources;
 import android.opengl.GLES30;
 import org.distorted.library.program.*;
 
-import java.io.InputStream;
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * A singleton class used to control various global settings.
@@ -136,7 +134,7 @@ public class Distorted
  */
   public static void onDestroy()
     {
-    DistortedRenderable.onDestroy();
+    DistortedSurface.onDestroy();
     DistortedTree.onDestroy();
     EffectQueue.onDestroy();
     DistortedEffects.onDestroy();
diff --git a/src/main/java/org/distorted/library/DistortedEffects.java b/src/main/java/org/distorted/library/DistortedEffects.java
index 8294912..df8f1af 100644
--- a/src/main/java/org/distorted/library/DistortedEffects.java
+++ b/src/main/java/org/distorted/library/DistortedEffects.java
@@ -194,8 +194,6 @@ 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  ];
@@ -209,8 +207,8 @@ public class DistortedEffects
       ndcX = X/W;
       ndcY = Y/W;
 
-      wx = (int)(projection.mWidth *(ndcX+1)/2);
-      wy = (int)(projection.mHeight*(ndcY+1)/2);
+      wx = (int)(surface.mWidth *(ndcX+1)/2);
+      wy = (int)(surface.mHeight*(ndcY+1)/2);
 
       if( wx<minx ) minx = wx;
       if( wx>maxx ) maxx = wx;
@@ -222,10 +220,10 @@ public class DistortedEffects
     surface.setAsOutput();
 
     Matrix.setIdentityM( mTmpMatrix, 0);
-    Matrix.translateM  ( mTmpMatrix, 0, minx-projection.mWidth/2, maxy-projection.mHeight/2, -projection.mDistance);
+    Matrix.translateM  ( mTmpMatrix, 0, minx-surface.mWidth/2, maxy-surface.mHeight/2, -surface.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, projection.mProjectionMatrix, 0, mTmpMatrix, 0);
+    Matrix.multiplyMM  ( mMVPMatrix, 0, surface.mProjectionMatrix, 0, mTmpMatrix, 0);
 
     GLES30.glUniform2f( mObjDH , 2*halfX, 2*halfY);
     GLES30.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix , 0);
@@ -244,14 +242,13 @@ public class DistortedEffects
     mP.compute(currTime);
 
     float halfZ = halfInputW*mesh.zFactor;
-    DistortedProjection projection = surface.getProjection();
-    GLES30.glViewport(0, 0, projection.mWidth, projection.mHeight);
+    GLES30.glViewport(0, 0, surface.mWidth, surface.mHeight);
 
     if( mP.mNumEffects==0 )
       {
       mProgram.useProgram();
       surface.setAsOutput();
-      mM.send(projection,halfInputW,halfInputH,halfZ);
+      mM.send(surface,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);
@@ -263,17 +260,17 @@ public class DistortedEffects
       {
       if( mV.mNumEffects==0 && mF.mNumEffects==0 && (mesh instanceof MeshFlat) && mM.canUseShortcut() )
         {
-        mM.constructMatrices(projection,halfInputW,halfInputH);
+        mM.constructMatrices(surface,halfInputW,halfInputH);
         mP.render(2*halfInputW, 2*halfInputH, mM.getMVP(), surface);
         }
       else
         {
         mProgram.useProgram();
-        mBufferFBO.resizeFast(projection.mWidth, projection.mHeight);
+        mBufferFBO.resizeFast(surface.mWidth, surface.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.getProjection(),halfInputW,halfInputH,halfZ);
+        mM.send(mBufferFBO,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);
@@ -282,11 +279,11 @@ public class DistortedEffects
         GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mesh.dataLength);
 
         Matrix.setIdentityM(mTmpMatrix, 0);
-        Matrix.translateM(mTmpMatrix, 0, 0, 0, -projection.mDistance);
-        Matrix.multiplyMM(mMVPMatrix, 0, projection.mProjectionMatrix, 0, mTmpMatrix, 0);
+        Matrix.translateM(mTmpMatrix, 0, 0, 0, -surface.mDistance);
+        Matrix.multiplyMM(mMVPMatrix, 0, surface.mProjectionMatrix, 0, mTmpMatrix, 0);
 
         mBufferFBO.setAsInput();
-        mP.render(projection.mWidth, projection.mHeight, mMVPMatrix, surface);
+        mP.render(surface.mWidth, surface.mHeight, mMVPMatrix, surface);
         }
       }
 
@@ -297,7 +294,7 @@ public class DistortedEffects
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
    
-  static void drawNoEffectsPriv(float halfInputW, float halfInputH, MeshObject mesh, DistortedProjection projection)
+  static void drawNoEffectsPriv(float halfInputW, float halfInputH, MeshObject mesh, DistortedOutputSurface projection)
     {
     GLES30.glViewport(0, 0, projection.mWidth, projection.mHeight);
 
diff --git a/src/main/java/org/distorted/library/DistortedFramebuffer.java b/src/main/java/org/distorted/library/DistortedFramebuffer.java
index 083c4c4..9b1ea76 100644
--- a/src/main/java/org/distorted/library/DistortedFramebuffer.java
+++ b/src/main/java/org/distorted/library/DistortedFramebuffer.java
@@ -28,12 +28,11 @@ import android.opengl.GLES30;
  * 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 implements DistortedInputSurface, DistortedOutputSurface
+public class DistortedFramebuffer extends DistortedOutputSurface implements DistortedInputSurface
   {
   private int[] mDepthH = new int[1];
   private int[] mFBOH   = new int[1];
   private boolean mDepthEnabled;
-  private DistortedProjection mProjection;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Must be called from a thread holding OpenGL Context
@@ -136,12 +135,16 @@ public class DistortedFramebuffer extends DistortedRenderable implements Distort
 // if new size fits into the size of the underlying Texture, just change the projection without
 // reallocating the Texture. Otherwise, we need to reallocate.
 //
-// Must be called form a thread holding the OpenGL context.
+// Must be called from a thread holding the OpenGL context.
 
   void resizeFast(int width, int height)
     {
-    if( mProjection.resize(width,height) )
+    if( mWidth!=width || mHeight!=height )
       {
+      mWidth = width;
+      mHeight= height;
+      createProjection();
+
       if( width> mSizeX || height> mSizeY)
         {
         mSizeX = width;
@@ -167,8 +170,6 @@ public class DistortedFramebuffer extends DistortedRenderable implements Distort
   public DistortedFramebuffer(int width, int height, boolean depthEnabled)
     {
     super(width,height,NOT_CREATED_YET);
-
-    mProjection  = new DistortedProjection(width,height);
     mDepthEnabled= depthEnabled;
     mFBOH[0]     = NOT_CREATED_YET;
     mDepthH[0]   = NOT_CREATED_YET;
@@ -186,8 +187,6 @@ public class DistortedFramebuffer extends DistortedRenderable implements Distort
   public DistortedFramebuffer(int width, int height)
     {
     super(width,height,NOT_CREATED_YET);
-
-    mProjection  = new DistortedProjection(width,height);
     mDepthEnabled= false;
     mFBOH[0]     = NOT_CREATED_YET;
     mDepthH[0]   = NOT_CREATED_YET;
@@ -196,6 +195,8 @@ public class DistortedFramebuffer extends DistortedRenderable implements Distort
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Bind the underlying rectangle of pixels as a OpenGL Texture.
+ *
+ * @returns <code>true</code> if successful.
  */
   public boolean setAsInput()
     {
@@ -240,77 +241,6 @@ public class DistortedFramebuffer extends DistortedRenderable implements Distort
     mDepthEnabled = enable;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * 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 Framebuffer.
- *
- * @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;
-      if( mColorH[0]>0 ) markForDeletion();
-      }
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Return the ID of the Texture (COLOR attachment 0) that's backing this FBO.
@@ -337,15 +267,4 @@ public class DistortedFramebuffer extends DistortedRenderable implements Distort
     {
     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
index 10ec49f..52c28e1 100644
--- a/src/main/java/org/distorted/library/DistortedInputSurface.java
+++ b/src/main/java/org/distorted/library/DistortedInputSurface.java
@@ -25,8 +25,24 @@ 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
+interface DistortedInputSurface
 {
+/**
+ * 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();
 /**
  * Take the underlying rectangle of pixels and bind this texture to OpenGL.
  */
diff --git a/src/main/java/org/distorted/library/DistortedOutputSurface.java b/src/main/java/org/distorted/library/DistortedOutputSurface.java
index 51aa36d..08d4143 100644
--- a/src/main/java/org/distorted/library/DistortedOutputSurface.java
+++ b/src/main/java/org/distorted/library/DistortedOutputSurface.java
@@ -19,20 +19,151 @@
 
 package org.distorted.library;
 
+import android.opengl.Matrix;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+abstract class DistortedOutputSurface extends DistortedSurface
+{
+  private float mX, mY, mFOV;
+  int mWidth,mHeight,mDepth;
+  float mDistance;
+  float[] mProjectionMatrix;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedOutputSurface(int width, int height, int color)
+    {
+    super(width,height,color);
+
+    mProjectionMatrix = new float[16];
+
+    mWidth = width;
+    mHeight= height;
+
+    mFOV = 60.0f;
+    mX   =  0.0f;
+    mY   =  0.0f;
+
+    createProjection();
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+  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);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * A Surface that we can set as Output, i.e. render to it.
+ * 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!
 
-public interface DistortedOutputSurface extends DistortedSurface
-{
+    if( surface.setAsInput() )
+      {
+      DistortedSurface.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)
+    {
+    DistortedSurface.deleteAllMarked();
+    create();
+    dt.drawRecursive(time,this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Bind this Surface as a Framebuffer we can render to.
  */
- void setAsOutput();
+  public abstract void setAsOutput();
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * 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)
+    {
+    mFOV = fov;
+    mX = x;
+    mY = y;
+
+    createProjection();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Return the Projection.
+ * Resize the underlying Framebuffer.
+ *
+ * @param width The new width.
+ * @param height The new height.
  */
- DistortedProjection getProjection();
-}
\ No newline at end of file
+  public void resize(int width, int height)
+    {
+    if( mWidth!=width || mHeight!=height )
+      {
+      mWidth = width;
+      mHeight= height;
+      createProjection();
+      mSizeX = width;
+      mSizeY = height;
+      if( mColorH[0]>0 ) markForDeletion();
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/library/DistortedProjection.java b/src/main/java/org/distorted/library/DistortedProjection.java
deleted file mode 100644
index 2e0e856..0000000
--- a/src/main/java/org/distorted/library/DistortedProjection.java
+++ /dev/null
@@ -1,124 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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
deleted file mode 100644
index 0209336..0000000
--- a/src/main/java/org/distorted/library/DistortedRenderable.java
+++ /dev/null
@@ -1,160 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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 java.util.Iterator;
-import java.util.LinkedList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * 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;
-  static final int DONT_CREATE      = -3;
-
-  private static boolean mListMarked = false;
-  private static LinkedList<DistortedRenderable> mList = new LinkedList<>();
-
-  private boolean mMarked;
-  int[] mColorH = new int[1];
-  int mSizeX, mSizeY;  // in screen space
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public abstract void create();
-  abstract void delete();
-  abstract void destroy();
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// must be called form a thread holding OpenGL Context
-
-  static synchronized void deleteAllMarked()
-    {
-    if( mListMarked )
-      {
-      DistortedRenderable tmp;
-      Iterator<DistortedRenderable> iterator = mList.iterator();
-
-      while(iterator.hasNext())
-        {
-        tmp = iterator.next();
-
-        if( tmp.mMarked )
-          {
-          tmp.delete();
-          tmp.mMarked = false;
-          iterator.remove();
-          }
-        }
-
-      mListMarked = false;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static synchronized void onDestroy()
-    {
-    for( DistortedRenderable ren : mList)
-      {
-      ren.destroy();
-      ren.mMarked = false;
-      }
-
-    mListMarked = false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  DistortedRenderable(int width, int height, int color)
-    {
-    mSizeX    = width ;
-    mSizeY    = height;
-    mColorH[0]= color;
-    mMarked   = false;
-    mList.add(this);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Mark the underlying OpenGL object for deletion. Actual deletion will take place on the next render.
- */
-  public void markForDeletion()
-    {
-    mListMarked = true;
-    mMarked     = true;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return unique ID of the Renderable.
- */
-  public long getID()
-    {
-    return mColorH[0];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Returns the height of the Renderable.
- *
- * @return height of the object, in pixels.
- */
-  public int getWidth()
-    {
-    return mSizeX;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the width of the Renderable.
- *
- * @return width of the Object, in pixels.
- */
-  public int getHeight()
-    {
-    return mSizeY;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the depth of the Renderable.
- * <p>
- * Admittedly quite a strange method. Why do we need to pass a Mesh to it? Because one cannot determine
- * 'depth' of a Renderable (bitmap really!) when rendered based only on the texture itself, that depends
- * on the Mesh it is rendered with.
- *
- * @return depth of the Object, in pixels.
- */
-  public int getDepth(MeshObject mesh)
-    {
-    return mesh==null ? 0 : (int)(mSizeX*mesh.zFactor);
-    }
-  }
diff --git a/src/main/java/org/distorted/library/DistortedScreen.java b/src/main/java/org/distorted/library/DistortedScreen.java
index d672ceb..ea12e0a 100644
--- a/src/main/java/org/distorted/library/DistortedScreen.java
+++ b/src/main/java/org/distorted/library/DistortedScreen.java
@@ -27,12 +27,10 @@ import android.opengl.GLES30;
  * <p>
  * User is able to render to it just like to a DistortedFramebuffer.
  */
-public class DistortedScreen extends DistortedRenderable implements DistortedOutputSurface
+public class DistortedScreen extends DistortedOutputSurface
   {
-  private int[] mFBOH   = new int[1];
-  private DistortedProjection mProjection;
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// here we don't manage underlying OpenGL assets ourselves
 
   public void create() {}
   void delete() {}
@@ -49,9 +47,6 @@ public class DistortedScreen extends DistortedRenderable implements DistortedOut
   public DistortedScreen()
     {
     super(0,0,DONT_CREATE);
-
-    mProjection  = new DistortedProjection();
-    mFBOH[0]     = 0;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -60,89 +55,8 @@ public class DistortedScreen extends DistortedRenderable implements DistortedOut
  */
   public void setAsOutput()
     {
-    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[0]);
+    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 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
index dc02eb4..4770090 100644
--- a/src/main/java/org/distorted/library/DistortedSurface.java
+++ b/src/main/java/org/distorted/library/DistortedSurface.java
@@ -19,28 +19,142 @@
 
 package org.distorted.library;
 
+import java.util.Iterator;
+import java.util.LinkedList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * 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 DistortedSurface
+  {
+  static final int FAILED_TO_CREATE = -1;
+  static final int NOT_CREATED_YET  = -2;
+  static final int DONT_CREATE      = -3;
+
+  private static boolean mListMarked = false;
+  private static LinkedList<DistortedSurface> mList = new LinkedList<>();
+
+  private boolean mMarked;
+  int[] mColorH = new int[1];
+  int mSizeX, mSizeY;  // in screen space
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public abstract void create();
+  abstract void delete();
+  abstract void destroy();
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called form a thread holding OpenGL Context
+
+  static synchronized void deleteAllMarked()
+    {
+    if( mListMarked )
+      {
+      DistortedSurface tmp;
+      Iterator<DistortedSurface> iterator = mList.iterator();
+
+      while(iterator.hasNext())
+        {
+        tmp = iterator.next();
+
+        if( tmp.mMarked )
+          {
+          tmp.delete();
+          tmp.mMarked = false;
+          iterator.remove();
+          }
+        }
+
+      mListMarked = false;
+      }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+  static synchronized void onDestroy()
+    {
+    for( DistortedSurface ren : mList)
+      {
+      ren.destroy();
+      ren.mMarked = false;
+      }
+
+    mListMarked = false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedSurface(int width, int height, int color)
+    {
+    mSizeX    = width ;
+    mSizeY    = height;
+    mColorH[0]= color;
+    mMarked   = false;
+    mList.add(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Abstract Surface.
+ * Mark the underlying OpenGL object for deletion. Actual deletion will take place on the next render.
  */
+  public void markForDeletion()
+    {
+    mListMarked = true;
+    mMarked     = true;
+    }
 
-interface DistortedSurface
-{
+////////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Create the underlying OpenGL part of the Surface.
+ * Return unique ID of the Surface.
  */
-  void create();
+  public long getID()
+    {
+    return mColorH[0];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
 /**
- * Return a unique ID of this Surface.
+ * Returns the height of the Surface.
+ *
+ * @return height of the object, in pixels.
  */
-  long getID();
+  public int getWidth()
+    {
+    return mSizeX;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Return the width of this Surface.
+ * Returns the width of the Surface.
+ *
+ * @return width of the Object, in pixels.
  */
-  int getWidth();
+  public int getHeight()
+    {
+    return mSizeY;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Return the height of this Surface.
+ * Returns the depth of the Surface.
+ * <p>
+ * Admittedly quite a strange method. Why do we need to pass a Mesh to it? Because one cannot determine
+ * 'depth' of a Surface (bitmap really!) when rendered based only on the texture itself, that depends
+ * on the Mesh it is rendered with.
+ *
+ * @return depth of the Object, in pixels.
  */
-  int getHeight();
-}
+  public int getDepth(MeshObject mesh)
+    {
+    return mesh==null ? 0 : (int)(mSizeX*mesh.zFactor);
+    }
+  }
diff --git a/src/main/java/org/distorted/library/DistortedTexture.java b/src/main/java/org/distorted/library/DistortedTexture.java
index 67799ff..6d5daa1 100644
--- a/src/main/java/org/distorted/library/DistortedTexture.java
+++ b/src/main/java/org/distorted/library/DistortedTexture.java
@@ -30,7 +30,7 @@ import android.opengl.GLUtils;
  * <p>
  * Create a Texture of arbitrary size and feed some pixels to it via the setTexture() method.
  */
-public class DistortedTexture extends DistortedRenderable implements DistortedInputSurface
+public class DistortedTexture extends DistortedSurface implements DistortedInputSurface
   {
   private Bitmap mBmp= null;
 
diff --git a/src/main/java/org/distorted/library/DistortedTree.java b/src/main/java/org/distorted/library/DistortedTree.java
index 8ce9e71..c9ab947 100644
--- a/src/main/java/org/distorted/library/DistortedTree.java
+++ b/src/main/java/org/distorted/library/DistortedTree.java
@@ -220,7 +220,7 @@ public class DistortedTree
         GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
 
         if( mSurface.setAsInput() )
-          DistortedEffects.drawNoEffectsPriv(halfX, halfY, mMesh, mData.mFBO.getProjection() );
+          DistortedEffects.drawNoEffectsPriv(halfX, halfY, mMesh, mData.mFBO );
 
         synchronized(this)
           {
diff --git a/src/main/java/org/distorted/library/EffectQueueMatrix.java b/src/main/java/org/distorted/library/EffectQueueMatrix.java
index 1219d0b..504dae8 100644
--- a/src/main/java/org/distorted/library/EffectQueueMatrix.java
+++ b/src/main/java/org/distorted/library/EffectQueueMatrix.java
@@ -107,7 +107,7 @@ class EffectQueueMatrix extends EffectQueue
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // here construct the ModelView and the ModelViewProjection Matrices
 
-  void constructMatrices(DistortedProjection projection, float halfX, float halfY)
+  void constructMatrices(DistortedOutputSurface projection, float halfX, float halfY)
     {
     Matrix.setIdentityM(mViewMatrix, 0);
     Matrix.translateM(mViewMatrix, 0, -projection.mWidth/2, projection.mHeight/2, -projection.mDistance);
@@ -284,7 +284,7 @@ class EffectQueueMatrix extends EffectQueue
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  synchronized void send(DistortedProjection projection, float halfX, float halfY, float halfZ)
+  synchronized void send(DistortedOutputSurface projection, float halfX, float halfY, float halfZ)
     {
     constructMatrices(projection,halfX,halfY);
 
@@ -297,7 +297,7 @@ class EffectQueueMatrix extends EffectQueue
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // here construct the ModelView Matrix, but without any effects
 
-  synchronized static void sendZero(DistortedProjection projection, float halfX, float halfY, float halfZ)
+  synchronized static void sendZero(DistortedOutputSurface projection, float halfX, float halfY, float halfZ)
     {
     Matrix.setIdentityM(mTmpMatrix, 0);
     Matrix.translateM(mTmpMatrix, 0, halfX-projection.mWidth/2, projection.mHeight/2-halfY, -projection.mDistance);
diff --git a/src/main/java/org/distorted/library/EffectQueuePostprocess.java b/src/main/java/org/distorted/library/EffectQueuePostprocess.java
index f33ec10..d32ded9 100644
--- a/src/main/java/org/distorted/library/EffectQueuePostprocess.java
+++ b/src/main/java/org/distorted/library/EffectQueuePostprocess.java
@@ -233,9 +233,6 @@ class EffectQueuePostprocess extends EffectQueue
     {
     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);
@@ -255,8 +252,8 @@ class EffectQueuePostprocess extends EffectQueue
     GLES30.glViewport(0, 0, (int)w, (int)h);
 
     Matrix.setIdentityM(mTmpMatrix, 0);
-    Matrix.translateM(mTmpMatrix, 0, 0, 0, -inputP.mDistance);
-    Matrix.multiplyMM(mMVPMatrix, 0, inputP.mProjectionMatrix, 0, mTmpMatrix, 0);
+    Matrix.translateM(mTmpMatrix, 0, 0, 0, -mBufferFBO.mDistance);
+    Matrix.multiplyMM(mMVPMatrix, 0, mBufferFBO.mProjectionMatrix, 0, mTmpMatrix, 0);
 
     // horizontal blur
     GLES30.glUniform1fv( mOffsetsH ,radius+1, mOffsets,0);
@@ -268,7 +265,7 @@ class EffectQueuePostprocess extends EffectQueue
     mBufferFBO.setAsInput();
     surface.setAsOutput();
 
-    GLES30.glViewport(0, 0, outputP.mWidth, outputP.mHeight);
+    GLES30.glViewport(0, 0, surface.mWidth, surface.mHeight);
 
     for(int i=0; i<=radius; i++) mOffsets[i] = offsetsCache[offset+i]/w;
 
