commit 0de83d97accef583dab6c8663cd5e5702e471b69
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Wed Mar 20 13:09:45 2019 +0000

    Fix several more apps for the 'center-of-matrix-effects-in-screen-center' change.

diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index b1b6d68..2378f7a 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -37,7 +37,7 @@
         <activity android:name=".quaternion.QuaternionActivity" />          
         <activity android:name=".effects3d.Effects3DActivity" />
         <activity android:name=".effects3d.Effects3DActivity2"/>
-        <activity android:name=".plainmonalisa.PlainMonaLisaActivity" />
+        <activity android:name=".surfaceview.SurfaceViewActivity" />
         <activity android:name=".save.SaveActivity"/>
         <activity android:name=".flag.FlagActivity"/>
         <activity android:name=".wind.WindActivity"/>
diff --git a/src/main/java/org/distorted/examples/TableOfContents.java b/src/main/java/org/distorted/examples/TableOfContents.java
index e32070b..5a56993 100644
--- a/src/main/java/org/distorted/examples/TableOfContents.java
+++ b/src/main/java/org/distorted/examples/TableOfContents.java
@@ -54,7 +54,7 @@ import org.distorted.examples.starwars.StarWarsActivity;
 import org.distorted.examples.inflate.InflateActivity;
 import org.distorted.examples.quaternion.QuaternionActivity;
 import org.distorted.examples.effects3d.Effects3DActivity;
-import org.distorted.examples.plainmonalisa.PlainMonaLisaActivity;
+import org.distorted.examples.surfaceview.SurfaceViewActivity;
 import org.distorted.examples.save.SaveActivity;
 import org.distorted.examples.flag.FlagActivity;
 import org.distorted.examples.wind.WindActivity;
@@ -278,7 +278,7 @@ public class TableOfContents extends ListActivity
       item.put(ITEM_TITLE, (i+1)+". "+getText(R.string.example_plainmonalisa));
       item.put(ITEM_SUBTITLE, getText(R.string.example_plainmonalisa_subtitle));
       data.add(item);
-      activityMapping.put(i++, PlainMonaLisaActivity.class);
+      activityMapping.put(i++, SurfaceViewActivity.class);
    }
 
    {
diff --git a/src/main/java/org/distorted/examples/flag/FlagRenderer.java b/src/main/java/org/distorted/examples/flag/FlagRenderer.java
index bf9cf3f..9f19687 100644
--- a/src/main/java/org/distorted/examples/flag/FlagRenderer.java
+++ b/src/main/java/org/distorted/examples/flag/FlagRenderer.java
@@ -24,7 +24,6 @@ import android.graphics.BitmapFactory;
 import android.opengl.GLSurfaceView;
 
 import org.distorted.examples.R;
-import org.distorted.library.effect.MatrixEffectMove;
 import org.distorted.library.effect.MatrixEffectQuaternion;
 import org.distorted.library.effect.MatrixEffectScale;
 import org.distorted.library.effect.VertexEffectWave;
@@ -55,7 +54,7 @@ class FlagRenderer implements GLSurfaceView.Renderer
     private Dynamic5D mWaveDyn;
     private Static5D mWaveSta1, mWaveSta2;
     private int mObjWidth, mObjHeight;
-    private Static3D mMove, mScale, mCenter;
+    private Static3D mScale;
 
     Static4D mQuat1, mQuat2;
     int mScreenMin;
@@ -88,14 +87,12 @@ class FlagRenderer implements GLSurfaceView.Renderer
       DistortedEffects effects = new DistortedEffects();
       effects.apply( new VertexEffectWave(mWaveDyn, waveCenter, waveRegion) );
 
-      mMove  = new Static3D(0,0,0);
       mScale = new Static3D(1,1,1);
-      mCenter= new Static3D(0,0,0);
+      Static3D center= new Static3D(0,0,0);
 
-      effects.apply( new MatrixEffectMove(mMove));
       effects.apply( new MatrixEffectScale(mScale));
-      effects.apply( new MatrixEffectQuaternion(mQuat1, mCenter) );
-      effects.apply( new MatrixEffectQuaternion(mQuat2, mCenter) );
+      effects.apply( new MatrixEffectQuaternion(mQuat1, center) );
+      effects.apply( new MatrixEffectQuaternion(mQuat2, center) );
 
       final int GRIDX = 50;
       final int GRIDY = 30;
@@ -160,9 +157,7 @@ class FlagRenderer implements GLSurfaceView.Renderer
       {
       mScreenMin = width<height ? width:height;
       float factor = ( width*mObjHeight > height*mObjWidth ) ? (0.8f*height)/mObjHeight : (0.8f*width)/mObjWidth;
-      mMove.set((width-factor*mObjWidth)/2 , (height-factor*mObjHeight)/2 , 0);
       mScale.set(factor,factor,factor);
-      mCenter.set(mObjWidth/2,mObjHeight/2, 0);
       mScreen.resize(width, height);
       }
 
diff --git a/src/main/java/org/distorted/examples/plainmonalisa/EglCore.java b/src/main/java/org/distorted/examples/plainmonalisa/EglCore.java
deleted file mode 100644
index 69054a4..0000000
--- a/src/main/java/org/distorted/examples/plainmonalisa/EglCore.java
+++ /dev/null
@@ -1,319 +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.examples.plainmonalisa;
-
-import android.graphics.SurfaceTexture;
-import android.opengl.EGL14;
-import android.opengl.EGLConfig;
-import android.opengl.EGLContext;
-import android.opengl.EGLDisplay;
-import android.opengl.EGLExt;
-import android.opengl.EGLSurface;
-import android.os.Build;
-import android.util.Log;
-import android.view.Surface;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public final class EglCore
-  {
-  private static final String TAG = "EglCore";
-
-  public static final int FLAG_RECORDABLE = 0x01;           // Surface must be recordable.  This discourages EGL from using a
-                                                            // pixel format that cannot be converted efficiently to something 
-                                                            // usable by the video encoder.
-  public static final int FLAG_TRY_GLES3 = 0x02;            // Ask for GLES3, fall back to GLES2 if not available.
-
-  private static final int EGL_RECORDABLE_ANDROID = 0x3142; // Android-specific extension
-
-  private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
-  private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
-  private EGLConfig mEGLConfig = null;
-  private int mGlVersion = -1;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public EglCore()
-    {
-    this(null, 0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public EglCore(EGLContext sharedContext, int flags)
-    {
-    if (mEGLDisplay != EGL14.EGL_NO_DISPLAY)
-      {
-      throw new RuntimeException("EGL already set up");
-      }
-
-    if (sharedContext == null)
-      {
-      sharedContext = EGL14.EGL_NO_CONTEXT;
-      }
-
-    mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
-
-    if (mEGLDisplay == EGL14.EGL_NO_DISPLAY)
-      {
-      throw new RuntimeException("unable to get EGL14 display");
-      }
-
-    int[] version = new int[2];
-
-    if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1))
-      {
-      mEGLDisplay = null;
-      throw new RuntimeException("unable to initialize EGL14");
-      }
-
-    // Try to get a GLES3 context, if requested.
-    if ((flags & FLAG_TRY_GLES3) != 0)
-      {
-      //Log.d(TAG, "Trying GLES 3");
-      EGLConfig config = getConfig(flags, 3);
-
-      if (config != null)
-        {
-        int[] attrib3_list =  { EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE };
-        EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attrib3_list, 0);
-
-        if (EGL14.eglGetError() == EGL14.EGL_SUCCESS)
-          {
-          //Log.d(TAG, "Got GLES 3 config");
-          mEGLConfig = config;
-          mEGLContext = context;
-          mGlVersion = 3;
-          }
-        }
-      }
-
-    if (mEGLContext == EGL14.EGL_NO_CONTEXT)
-      {  // GLES 2 only, or GLES 3 attempt failed
-         //Log.d(TAG, "Trying GLES 2");
-      EGLConfig config = getConfig(flags, 2);
-
-      if (config == null)
-        {
-        throw new RuntimeException("Unable to find a suitable EGLConfig");
-        }
-      int[] attrib2_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE };
-      EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attrib2_list, 0);
-      checkEglError("eglCreateContext");
-      mEGLConfig = config;
-      mEGLContext = context;
-      mGlVersion = 2;
-      }
-
-    // Confirm with query.
-    int[] values = new int[1];
-    EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values, 0);
-
-    Log.d(TAG, "EGLContext created, client version " + values[0]);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private EGLConfig getConfig(int flags, int version)
-    {
-    int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
-
-    if (version >= 3)
-      {
-      renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR;
-      }
-
-    // The actual surface is generally RGBA or RGBX, so situationally omitting alpha
-    // doesn't really help.  It can also lead to a huge performance hit on glReadPixels()
-    // when reading into a GL_RGBA buffer.
-
-    int [] normalAttribList =
-                {
-                EGL14.EGL_RED_SIZE, 8,
-                EGL14.EGL_GREEN_SIZE, 8,
-                EGL14.EGL_BLUE_SIZE, 8,
-                EGL14.EGL_ALPHA_SIZE, 8,
-                EGL14.EGL_RENDERABLE_TYPE, renderableType,
-                EGL14.EGL_NONE, 0,      // placeholder for recordable [@-3]
-                EGL14.EGL_NONE
-                };
-
-    if ((flags & FLAG_RECORDABLE) != 0)
-      {
-      normalAttribList[normalAttribList.length - 3] = EGL_RECORDABLE_ANDROID;
-      normalAttribList[normalAttribList.length - 2] = 1;
-      }
-
-    int [] emulatorAttribList =
-                {
-                EGL14.EGL_RED_SIZE, 8,
-                EGL14.EGL_GREEN_SIZE, 8,
-                EGL14.EGL_BLUE_SIZE, 8,
-                EGL14.EGL_ALPHA_SIZE, 8,
-                EGL14.EGL_DEPTH_SIZE, 16,
-                EGL14.EGL_STENCIL_SIZE, 0,
-                EGL14.EGL_NONE
-                };
-
-    boolean isEmulator=
-               Build.FINGERPRINT.startsWith("generic")
-            || Build.FINGERPRINT.startsWith("unknown")
-            || Build.MODEL.contains("google_sdk")
-            || Build.MODEL.contains("Emulator")
-            || Build.MODEL.contains("Android SDK built for x86")
-            || Build.MANUFACTURER.contains("Genymotion")
-            || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
-            || "google_sdk".equals(Build.PRODUCT);
-
-    int [] attribList = (isEmulator ? emulatorAttribList:normalAttribList);
-
-    if( isEmulator )
-      {
-      Log.w(TAG, "Using emulator config!" );
-      }
-
-    EGLConfig[] configs = new EGLConfig[1];
-    int[] numConfigs = new int[1];
-
-    if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0))
-      {
-      Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig");
-      return null;
-      }
-
-    return configs[0];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void release()
-    {
-    if (mEGLDisplay != EGL14.EGL_NO_DISPLAY)
-      {
-      EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
-      EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
-      EGL14.eglReleaseThread();
-      EGL14.eglTerminate(mEGLDisplay);
-      }
-
-    mEGLDisplay = EGL14.EGL_NO_DISPLAY;
-    mEGLContext = EGL14.EGL_NO_CONTEXT;
-    mEGLConfig = null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  @Override
-  protected void finalize() throws Throwable
-    {
-    try
-      {
-      if (mEGLDisplay != EGL14.EGL_NO_DISPLAY)
-        {
-        // We're limited here -- finalizers don't run on the thread that holds
-        // the EGL state, so if a surface or context is still current on another
-        // thread we can't fully release it here.  Exceptions thrown from here
-        // are quietly discarded.  Complain in the log file.
-        Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked");
-        release();
-        }
-      }
-    finally
-      {
-      super.finalize();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void releaseSurface(EGLSurface eglSurface)
-    {
-    EGL14.eglDestroySurface(mEGLDisplay, eglSurface);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public EGLSurface createWindowSurface(Object surface)
-    {
-    if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture))
-      {
-      throw new RuntimeException("invalid surface: " + surface);
-      }
-
-    int[] surfaceAttribs = { EGL14.EGL_NONE };
-    EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, surfaceAttribs, 0);
-    checkEglError("eglCreateWindowSurface");
-
-    if (eglSurface == null)
-      {
-      throw new RuntimeException("surface was null");
-      }
-    return eglSurface;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void makeCurrent(EGLSurface eglSurface)
-    {
-    if (mEGLDisplay == EGL14.EGL_NO_DISPLAY)
-      {
-      Log.d(TAG, "NOTE: makeCurrent w/o display");
-      }
-    if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext))
-      {
-      throw new RuntimeException("eglMakeCurrent failed");
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void makeNothingCurrent()
-    {
-    if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT))
-      {
-      throw new RuntimeException("eglMakeCurrent failed");
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getGlVersion()
-    {
-    return mGlVersion;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean swapBuffers(EGLSurface eglSurface)
-    {
-    return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void checkEglError(String msg)
-    {
-    int error;
-
-    if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS)
-      {
-      throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
-      }
-    }
-  }
diff --git a/src/main/java/org/distorted/examples/plainmonalisa/PlainMonaLisaActivity.java b/src/main/java/org/distorted/examples/plainmonalisa/PlainMonaLisaActivity.java
deleted file mode 100644
index 7a93e51..0000000
--- a/src/main/java/org/distorted/examples/plainmonalisa/PlainMonaLisaActivity.java
+++ /dev/null
@@ -1,71 +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.examples.plainmonalisa;
-
-import org.distorted.library.main.Distorted;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class PlainMonaLisaActivity extends Activity
-{
-    private PlainMonaLisaSurfaceView mView;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onCreate(Bundle icicle) 
-      {
-      super.onCreate(icicle);
-      mView = new PlainMonaLisaSurfaceView(this);
-      setContentView(mView);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onPause() 
-      {
-      mView.onPause();
-      Distorted.onPause();
-      super.onPause();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onResume() 
-      {
-      super.onResume();
-      mView.onResume();
-      }
-    
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onDestroy() 
-      {
-      Distorted.onDestroy();  
-      super.onDestroy();
-      }
-    
-}
diff --git a/src/main/java/org/distorted/examples/plainmonalisa/PlainMonaLisaSurfaceView.java b/src/main/java/org/distorted/examples/plainmonalisa/PlainMonaLisaSurfaceView.java
deleted file mode 100644
index 7b9b788..0000000
--- a/src/main/java/org/distorted/examples/plainmonalisa/PlainMonaLisaSurfaceView.java
+++ /dev/null
@@ -1,159 +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.examples.plainmonalisa;
-
-import android.content.Context;
-import android.view.Choreographer;
-import android.view.SurfaceView;
-import android.view.SurfaceHolder;
-import android.util.Log;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class PlainMonaLisaSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Choreographer.FrameCallback
-  {
-  private static final String TAG = "MonaLisaSurface";
-  private RenderThread mRenderThread;
-  private static volatile boolean mPaused = true;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public PlainMonaLisaSurfaceView(Context context)
-    {
-    super(context);
-    getHolder().addCallback(this);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @Override
-  public void surfaceCreated(SurfaceHolder holder)
-    {
-    Log.d(TAG, "surfaceCreated holder=" + holder);
-
-    mRenderThread = new RenderThread(holder, this);
-    mRenderThread.setName("GL render");
-    mRenderThread.start();
-    mRenderThread.waitUntilReady();
-
-    RenderHandler rh = mRenderThread.getHandler();
-
-    if (rh != null)
-      {
-      rh.sendSurfaceCreated();
-      }
-
-    Choreographer.getInstance().postFrameCallback(this);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @Override
-  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
-    {
-    Log.d(TAG, "surfaceChanged fmt=" + format + " size=" + width + "x" + height +" holder=" + holder);
-
-    RenderHandler rh = mRenderThread.getHandler();
-
-    if (rh != null)
-      {
-      rh.sendSurfaceChanged(format, width, height);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @Override
-  public void surfaceDestroyed(SurfaceHolder holder)
-    {
-    Log.d(TAG, "surfaceDestroyed holder=" + holder);
-
-    // We need to wait for the render thread to shut down before continuing because we
-    // don't want the Surface to disappear out from under it mid-render.  The frame
-    // notifications will have been stopped back in onPause(), but there might have
-    // been one in progress.
-
-    RenderHandler rh = mRenderThread.getHandler();
-
-    if (rh != null)
-      {
-      rh.sendShutdown();
-
-      try
-        {
-        mRenderThread.join();
-        }
-      catch (InterruptedException ie)
-        {
-        throw new RuntimeException("join was interrupted", ie);
-        }
-      }
-    mRenderThread = null;
-
-    Log.d(TAG, "surfaceDestroyed complete");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onPause()
-    {
-    mPaused = true;
-    RenderThread.setResourcesCreated();
-
-    Log.d(TAG, "onPause unhooking choreographer");
-    Choreographer.getInstance().removeFrameCallback(this);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onResume()
-    {
-    mPaused = false;
-
-    if (mRenderThread != null)
-      {
-      Log.d(TAG, "onResume re-hooking choreographer");
-      Choreographer.getInstance().postFrameCallback(this);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static boolean isPaused()
-    {
-    return mPaused;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @Override
-  public void doFrame(long frameTimeNanos)
-    {
-    RenderHandler rh = mRenderThread.getHandler();
-
-    if (rh != null)
-      {
-      Choreographer.getInstance().postFrameCallback(this);
-      rh.sendDoFrame(frameTimeNanos);
-      }
-    }
-  }
-
-
diff --git a/src/main/java/org/distorted/examples/plainmonalisa/RenderHandler.java b/src/main/java/org/distorted/examples/plainmonalisa/RenderHandler.java
deleted file mode 100644
index d36965a..0000000
--- a/src/main/java/org/distorted/examples/plainmonalisa/RenderHandler.java
+++ /dev/null
@@ -1,107 +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.examples.plainmonalisa;
-
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Used for messages sent from the UI thread to the render thread.
-
-class RenderHandler extends Handler
-  {
-  private static final String TAG = "RenderHandler";
-
-  private static final int MSG_SURFACE_CREATED = 0;
-  private static final int MSG_SURFACE_CHANGED = 1;
-  private static final int MSG_DO_FRAME = 2;
-  private static final int MSG_SHUTDOWN = 4;
-
-  private WeakReference<RenderThread> mWeakRenderThread;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  RenderHandler(RenderThread rt)
-    {
-    mWeakRenderThread = new WeakReference<>(rt);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void sendSurfaceCreated()
-    {
-    sendMessage(obtainMessage(RenderHandler.MSG_SURFACE_CREATED));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void sendSurfaceChanged(int format, int width, int height)
-    {
-    sendMessage(obtainMessage(RenderHandler.MSG_SURFACE_CHANGED, width, height));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void sendDoFrame(long frameTimeNanos)
-    {
-    sendMessage(obtainMessage(RenderHandler.MSG_DO_FRAME, (int) (frameTimeNanos >> 32), (int) frameTimeNanos));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void sendShutdown()
-    {
-    sendMessage(obtainMessage(RenderHandler.MSG_SHUTDOWN));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  @Override  // runs on RenderThread
-  public void handleMessage(Message msg)
-    {
-    int what = msg.what;
-
-    RenderThread renderThread = mWeakRenderThread.get();
-
-    if (renderThread == null)
-      {
-      Log.w(TAG, "RenderHandler.handleMessage: weak ref is null");
-      return;
-      }
-
-    switch (what)
-      {
-      case MSG_SURFACE_CREATED: renderThread.surfaceCreated();
-                                break;
-      case MSG_SURFACE_CHANGED: renderThread.surfaceChanged(msg.arg1, msg.arg2);
-                                break;
-      case MSG_DO_FRAME:        long timestamp = (((long) msg.arg1) << 32) | (((long) msg.arg2) & 0xffffffffL);
-                                renderThread.doFrame(timestamp);
-                                break;
-      case MSG_SHUTDOWN:        renderThread.shutdown();
-                                break;
-      default:                  throw new RuntimeException("unknown message " + what);
-      }
-    }
-  }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/examples/plainmonalisa/RenderThread.java b/src/main/java/org/distorted/examples/plainmonalisa/RenderThread.java
deleted file mode 100644
index cabc8db..0000000
--- a/src/main/java/org/distorted/examples/plainmonalisa/RenderThread.java
+++ /dev/null
@@ -1,309 +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.examples.plainmonalisa;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.opengl.EGL14;
-import android.opengl.EGLSurface;
-import android.opengl.GLES31;
-import android.os.Looper;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.effect.MatrixEffectScale;
-import org.distorted.library.effect.VertexEffectDistort;
-import org.distorted.library.main.Distorted;
-import org.distorted.library.main.DistortedEffects;
-import org.distorted.library.main.DistortedScreen;
-import org.distorted.library.mesh.MeshFlat;
-import org.distorted.library.main.DistortedTexture;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static4D;
-import org.distorted.examples.R;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class RenderThread extends Thread
-  {
-  private static final String TAG = "RenderThread";
-
-  // Object must be created on render thread to get correct Looper, but is used from
-  // UI thread, so we need to declare it volatile to ensure the UI thread sees a fully
-  // constructed object.
-  private volatile RenderHandler mHandler;
-
-  // Used to wait for the thread to start.
-  private final Object mStartLock = new Object();
-  private boolean mReady = false;
-  private volatile SurfaceHolder mSurfaceHolder;  // may be updated by UI thread
-  private EglCore eglCore;
-  private EGLSurface eglSurface;
-
-  private DistortedEffects mEffects;
-  private DistortedTexture mTexture;
-  private MeshFlat mMesh;
-  private DistortedScreen mScreen;
-  private int bmpHeight, bmpWidth;
-  private SurfaceView mView;
-  private static boolean resourcesCreated = false;
-  private Static3D mMove, mScale;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  RenderThread(SurfaceHolder holder, SurfaceView view)
-    {
-    mSurfaceHolder = holder;
-    mView = view;
-
-    Static3D pLeft = new Static3D( 90, 108, 0);
-    Static3D pRight= new Static3D(176, 111, 0);
-
-    Static4D rLeft = new Static4D(-10, 10, 0,25);
-    Static4D rRight= new Static4D( 10,  5, 0,25);
-
-    Dynamic3D dLeft = new Dynamic3D(1000,0.0f);
-    Dynamic3D dRight= new Dynamic3D(1000,0.0f);
-
-    dLeft.add( new Static3D(  0,  0, 0) );
-    dLeft.add( new Static3D(-20, 20, 0) );
-
-    dRight.add( new Static3D(  0,  0, 0) );
-    dRight.add( new Static3D( 20, 10, 0) );
-
-    mEffects = new DistortedEffects();
-    mEffects.apply( new VertexEffectDistort(dLeft , pLeft , rLeft ) );
-    mEffects.apply( new VertexEffectDistort(dRight, pRight, rRight) );
-
-    mMove = new Static3D(0,0,0);
-    mScale= new Static3D(1,1,1);
-    mEffects.apply(new MatrixEffectMove(mMove));
-    mEffects.apply(new MatrixEffectScale(mScale));
-
-    mScreen = new DistortedScreen();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void setResourcesCreated()
-    {
-    resourcesCreated = false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void checkGlError(String op)
-    {
-    int error = GLES31.glGetError();
-
-    if (error != GLES31.GL_NO_ERROR)
-      {
-      String msg = op + ": glError 0x" + Integer.toHexString(error);
-      Log.e(TAG, msg);
-//    throw new RuntimeException(msg);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @Override
-  public void run()
-    {
-    Looper.prepare();
-    mHandler = new RenderHandler(this);
-    eglCore = new EglCore(null, EglCore.FLAG_RECORDABLE | EglCore.FLAG_TRY_GLES3);
-
-    synchronized (mStartLock)
-      {
-      mReady = true;
-      mStartLock.notify();    // signal waitUntilReady()
-      }
-
-    Looper.loop();
-    Log.d(TAG, "looper quit");
-
-    checkGlError("releaseGl start");
-
-    if (eglSurface != null)
-      {
-      eglCore.releaseSurface(eglSurface);
-      eglSurface = EGL14.EGL_NO_SURFACE;
-      }
-
-    checkGlError("releaseGl done");
-
-    eglCore.makeNothingCurrent();
-    eglCore.release();
-
-    synchronized (mStartLock)
-      {
-      mReady = false;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void waitUntilReady()
-    {
-    synchronized (mStartLock)
-      {
-      while (!mReady)
-        {
-        try
-          {
-          mStartLock.wait();
-          }
-        catch (InterruptedException ie) {  }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void shutdown()
-    {
-    Log.d(TAG, "shutdown");
-    Looper.myLooper().quit();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  RenderHandler getHandler()
-    {
-    return mHandler;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void surfaceCreated()
-    {
-    Log.d(TAG, "surfaceCreated");
-
-    Surface surface = mSurfaceHolder.getSurface();
-
-    eglSurface = eglCore.createWindowSurface(surface);
-    eglCore.makeCurrent(eglSurface);
-
-    createResources();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void surfaceChanged(int width, int height)
-    {
-    Log.d(TAG, "surfaceChanged " + width + "x" + height);
-
-    if( (float)bmpHeight/bmpWidth > (float)height/width )
-      {
-      int w = (height*bmpWidth)/bmpHeight;
-      float factor = (float)height/bmpHeight;
-
-      mMove.set((width-w)/2,0,0);
-      mScale.set( factor,factor,factor );
-      }
-    else
-      {
-      int h = (width*bmpHeight)/bmpWidth;
-      float factor = (float)width/bmpWidth;
-
-      mMove.set(0,(height-h)/2,0);
-      mScale.set( factor,factor,factor );
-      }
-
-    mScreen.resize(width, height);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createResources()
-    {
-    resourcesCreated = true;
-
-    InputStream is = mView.getContext().getResources().openRawResource(R.raw.monalisa);
-    Bitmap bmp;
-
-    try
-      {
-      bmp = BitmapFactory.decodeStream(is);
-      }
-    finally
-      {
-      try
-        {
-        is.close();
-        }
-      catch(IOException io) {}
-      }
-
-    bmpHeight = bmp.getHeight();
-    bmpWidth  = bmp.getWidth();
-
-    if( mTexture==null ) mTexture = new DistortedTexture(bmpWidth,bmpHeight);
-    mTexture.setTexture(bmp);
-
-    if( mMesh==null ) mMesh = new MeshFlat(9,9*bmpHeight/bmpWidth);
-
-    mScreen.detachAll();
-    mScreen.attach(mTexture,mEffects,mMesh);
-
-    VertexEffectDistort.enable();
-
-    try
-      {
-      Distorted.onCreate(mView.getContext());
-      }
-    catch(Exception ex)
-      {
-      Log.e("PlainMonaLisa", ex.getMessage() );
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void doFrame(long frameTimeNs)
-    {
-    // We can still get here after onPaused - ignore such calls.
-    if( PlainMonaLisaSurfaceView.isPaused() )
-      {
-      android.util.Log.e("Thread", "Got here after onPaused- ignoring frame draw call!!");
-      return;
-      }
-
-    // This will happen if we just briefly went into the background (press 'POWER')
-    // Then surfaceCreated/changed is not called
-    if( !resourcesCreated )
-      {
-      Log.e("Thread", "resources not created!!");
-      createResources();
-      }
-
-    eglCore.makeCurrent(eglSurface);
-    mScreen.render( frameTimeNs/1000000 );  // 1 nanosecond = 1 millisecond / 10^-6
-    eglCore.swapBuffers(eglSurface);
-    }
-
-  }
diff --git a/src/main/java/org/distorted/examples/save/SaveRenderer.java b/src/main/java/org/distorted/examples/save/SaveRenderer.java
index 489353f..7c282a4 100644
--- a/src/main/java/org/distorted/examples/save/SaveRenderer.java
+++ b/src/main/java/org/distorted/examples/save/SaveRenderer.java
@@ -30,7 +30,6 @@ import javax.microedition.khronos.opengles.GL10;
 
 import org.distorted.examples.R;
 
-import org.distorted.library.effect.MatrixEffectMove;
 import org.distorted.library.effect.MatrixEffectScale;
 import org.distorted.library.effect.VertexEffectSink;
 import org.distorted.library.main.Distorted;
@@ -61,7 +60,7 @@ class SaveRenderer implements GLSurfaceView.Renderer
   private MeshFlat mMesh;
   private DistortedScreen mScreen;
   private Static1D s0;
-  private Static3D mScaleFactor, mScaleMain, mMove;
+  private Static3D mScaleFactor, mScaleMain;
 
   private float mScale;
   private int bmpHeight, bmpWidth;
@@ -90,13 +89,11 @@ class SaveRenderer implements GLSurfaceView.Renderer
 
     mScale = 1.0f;
     mScaleFactor = new Static3D(mScale,mScale,1.0f);
-    mMove = new Static3D(0,0,0);
     mScaleMain = new Static3D(1,1,1);
 
     mEffects = new DistortedEffects();
     mEffects.apply( new VertexEffectSink(diSink, pLeft , sinkRegion) );
     mEffects.apply( new VertexEffectSink(diSink, pRight, sinkRegion) );
-    mEffects.apply( new MatrixEffectMove(mMove));
     mEffects.apply( new MatrixEffectScale(mScaleMain));
     mEffects.apply( new MatrixEffectScale(mScaleFactor));
 
@@ -155,7 +152,6 @@ class SaveRenderer implements GLSurfaceView.Renderer
     {
     if( isSaving )  // render to an offscreen buffer and read pixels
       {
-      mMove.set(0,0,0);
       mScaleMain.set(1,1,1);
       mOffscreen.render(System.currentTimeMillis());
       applyMatrixEffects(scrWidth,scrHeight);
@@ -194,22 +190,10 @@ class SaveRenderer implements GLSurfaceView.Renderer
 
   private void applyMatrixEffects(int width, int height)
     {
-    if( (float)bmpHeight/bmpWidth > (float)height/width )
-      {
-      int w = (height*bmpWidth)/bmpHeight;
-      float factor = (float)height/bmpHeight;
-
-      mMove.set((width-w)/2,0,0);
-      mScaleMain.set(factor,factor,factor);
-      }
-    else
-      {
-      int h = (width*bmpHeight)/bmpWidth;
-      float factor = (float)width/bmpWidth;
-
-      mMove.set(0,(height-h)/2,0);
-      mScaleMain.set(factor,factor,factor);
-      }
+    float horiRatio = (float)width / mTexture.getWidth();
+    float vertRatio = (float)height/ mTexture.getHeight();
+    float factor    = horiRatio > vertRatio ? vertRatio : horiRatio;
+    mScaleMain.set(factor,factor,factor);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/examples/surfaceview/EglCore.java b/src/main/java/org/distorted/examples/surfaceview/EglCore.java
new file mode 100644
index 0000000..65825eb
--- /dev/null
+++ b/src/main/java/org/distorted/examples/surfaceview/EglCore.java
@@ -0,0 +1,319 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.examples.surfaceview;
+
+import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLExt;
+import android.opengl.EGLSurface;
+import android.os.Build;
+import android.util.Log;
+import android.view.Surface;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public final class EglCore
+  {
+  private static final String TAG = "EglCore";
+
+  public static final int FLAG_RECORDABLE = 0x01;           // Surface must be recordable.  This discourages EGL from using a
+                                                            // pixel format that cannot be converted efficiently to something 
+                                                            // usable by the video encoder.
+  public static final int FLAG_TRY_GLES3 = 0x02;            // Ask for GLES3, fall back to GLES2 if not available.
+
+  private static final int EGL_RECORDABLE_ANDROID = 0x3142; // Android-specific extension
+
+  private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
+  private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
+  private EGLConfig mEGLConfig = null;
+  private int mGlVersion = -1;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public EglCore()
+    {
+    this(null, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public EglCore(EGLContext sharedContext, int flags)
+    {
+    if (mEGLDisplay != EGL14.EGL_NO_DISPLAY)
+      {
+      throw new RuntimeException("EGL already set up");
+      }
+
+    if (sharedContext == null)
+      {
+      sharedContext = EGL14.EGL_NO_CONTEXT;
+      }
+
+    mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+
+    if (mEGLDisplay == EGL14.EGL_NO_DISPLAY)
+      {
+      throw new RuntimeException("unable to get EGL14 display");
+      }
+
+    int[] version = new int[2];
+
+    if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1))
+      {
+      mEGLDisplay = null;
+      throw new RuntimeException("unable to initialize EGL14");
+      }
+
+    // Try to get a GLES3 context, if requested.
+    if ((flags & FLAG_TRY_GLES3) != 0)
+      {
+      //Log.d(TAG, "Trying GLES 3");
+      EGLConfig config = getConfig(flags, 3);
+
+      if (config != null)
+        {
+        int[] attrib3_list =  { EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE };
+        EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attrib3_list, 0);
+
+        if (EGL14.eglGetError() == EGL14.EGL_SUCCESS)
+          {
+          //Log.d(TAG, "Got GLES 3 config");
+          mEGLConfig = config;
+          mEGLContext = context;
+          mGlVersion = 3;
+          }
+        }
+      }
+
+    if (mEGLContext == EGL14.EGL_NO_CONTEXT)
+      {  // GLES 2 only, or GLES 3 attempt failed
+         //Log.d(TAG, "Trying GLES 2");
+      EGLConfig config = getConfig(flags, 2);
+
+      if (config == null)
+        {
+        throw new RuntimeException("Unable to find a suitable EGLConfig");
+        }
+      int[] attrib2_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE };
+      EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attrib2_list, 0);
+      checkEglError("eglCreateContext");
+      mEGLConfig = config;
+      mEGLContext = context;
+      mGlVersion = 2;
+      }
+
+    // Confirm with query.
+    int[] values = new int[1];
+    EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values, 0);
+
+    Log.d(TAG, "EGLContext created, client version " + values[0]);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private EGLConfig getConfig(int flags, int version)
+    {
+    int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
+
+    if (version >= 3)
+      {
+      renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR;
+      }
+
+    // The actual surface is generally RGBA or RGBX, so situationally omitting alpha
+    // doesn't really help.  It can also lead to a huge performance hit on glReadPixels()
+    // when reading into a GL_RGBA buffer.
+
+    int [] normalAttribList =
+                {
+                EGL14.EGL_RED_SIZE, 8,
+                EGL14.EGL_GREEN_SIZE, 8,
+                EGL14.EGL_BLUE_SIZE, 8,
+                EGL14.EGL_ALPHA_SIZE, 8,
+                EGL14.EGL_RENDERABLE_TYPE, renderableType,
+                EGL14.EGL_NONE, 0,      // placeholder for recordable [@-3]
+                EGL14.EGL_NONE
+                };
+
+    if ((flags & FLAG_RECORDABLE) != 0)
+      {
+      normalAttribList[normalAttribList.length - 3] = EGL_RECORDABLE_ANDROID;
+      normalAttribList[normalAttribList.length - 2] = 1;
+      }
+
+    int [] emulatorAttribList =
+                {
+                EGL14.EGL_RED_SIZE, 8,
+                EGL14.EGL_GREEN_SIZE, 8,
+                EGL14.EGL_BLUE_SIZE, 8,
+                EGL14.EGL_ALPHA_SIZE, 8,
+                EGL14.EGL_DEPTH_SIZE, 16,
+                EGL14.EGL_STENCIL_SIZE, 0,
+                EGL14.EGL_NONE
+                };
+
+    boolean isEmulator=
+               Build.FINGERPRINT.startsWith("generic")
+            || Build.FINGERPRINT.startsWith("unknown")
+            || Build.MODEL.contains("google_sdk")
+            || Build.MODEL.contains("Emulator")
+            || Build.MODEL.contains("Android SDK built for x86")
+            || Build.MANUFACTURER.contains("Genymotion")
+            || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
+            || "google_sdk".equals(Build.PRODUCT);
+
+    int [] attribList = (isEmulator ? emulatorAttribList:normalAttribList);
+
+    if( isEmulator )
+      {
+      Log.w(TAG, "Using emulator config!" );
+      }
+
+    EGLConfig[] configs = new EGLConfig[1];
+    int[] numConfigs = new int[1];
+
+    if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0))
+      {
+      Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig");
+      return null;
+      }
+
+    return configs[0];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void release()
+    {
+    if (mEGLDisplay != EGL14.EGL_NO_DISPLAY)
+      {
+      EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
+      EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
+      EGL14.eglReleaseThread();
+      EGL14.eglTerminate(mEGLDisplay);
+      }
+
+    mEGLDisplay = EGL14.EGL_NO_DISPLAY;
+    mEGLContext = EGL14.EGL_NO_CONTEXT;
+    mEGLConfig = null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected void finalize() throws Throwable
+    {
+    try
+      {
+      if (mEGLDisplay != EGL14.EGL_NO_DISPLAY)
+        {
+        // We're limited here -- finalizers don't run on the thread that holds
+        // the EGL state, so if a surface or context is still current on another
+        // thread we can't fully release it here.  Exceptions thrown from here
+        // are quietly discarded.  Complain in the log file.
+        Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked");
+        release();
+        }
+      }
+    finally
+      {
+      super.finalize();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void releaseSurface(EGLSurface eglSurface)
+    {
+    EGL14.eglDestroySurface(mEGLDisplay, eglSurface);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public EGLSurface createWindowSurface(Object surface)
+    {
+    if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture))
+      {
+      throw new RuntimeException("invalid surface: " + surface);
+      }
+
+    int[] surfaceAttribs = { EGL14.EGL_NONE };
+    EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, surfaceAttribs, 0);
+    checkEglError("eglCreateWindowSurface");
+
+    if (eglSurface == null)
+      {
+      throw new RuntimeException("surface was null");
+      }
+    return eglSurface;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void makeCurrent(EGLSurface eglSurface)
+    {
+    if (mEGLDisplay == EGL14.EGL_NO_DISPLAY)
+      {
+      Log.d(TAG, "NOTE: makeCurrent w/o display");
+      }
+    if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext))
+      {
+      throw new RuntimeException("eglMakeCurrent failed");
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void makeNothingCurrent()
+    {
+    if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT))
+      {
+      throw new RuntimeException("eglMakeCurrent failed");
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getGlVersion()
+    {
+    return mGlVersion;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean swapBuffers(EGLSurface eglSurface)
+    {
+    return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void checkEglError(String msg)
+    {
+    int error;
+
+    if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS)
+      {
+      throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
+      }
+    }
+  }
diff --git a/src/main/java/org/distorted/examples/surfaceview/RenderHandler.java b/src/main/java/org/distorted/examples/surfaceview/RenderHandler.java
new file mode 100644
index 0000000..5b793a9
--- /dev/null
+++ b/src/main/java/org/distorted/examples/surfaceview/RenderHandler.java
@@ -0,0 +1,107 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.examples.surfaceview;
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Used for messages sent from the UI thread to the render thread.
+
+class RenderHandler extends Handler
+  {
+  private static final String TAG = "RenderHandler";
+
+  private static final int MSG_SURFACE_CREATED = 0;
+  private static final int MSG_SURFACE_CHANGED = 1;
+  private static final int MSG_DO_FRAME = 2;
+  private static final int MSG_SHUTDOWN = 4;
+
+  private WeakReference<RenderThread> mWeakRenderThread;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RenderHandler(RenderThread rt)
+    {
+    mWeakRenderThread = new WeakReference<>(rt);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void sendSurfaceCreated()
+    {
+    sendMessage(obtainMessage(RenderHandler.MSG_SURFACE_CREATED));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void sendSurfaceChanged(int format, int width, int height)
+    {
+    sendMessage(obtainMessage(RenderHandler.MSG_SURFACE_CHANGED, width, height));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void sendDoFrame(long frameTimeNanos)
+    {
+    sendMessage(obtainMessage(RenderHandler.MSG_DO_FRAME, (int) (frameTimeNanos >> 32), (int) frameTimeNanos));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void sendShutdown()
+    {
+    sendMessage(obtainMessage(RenderHandler.MSG_SHUTDOWN));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  @Override  // runs on RenderThread
+  public void handleMessage(Message msg)
+    {
+    int what = msg.what;
+
+    RenderThread renderThread = mWeakRenderThread.get();
+
+    if (renderThread == null)
+      {
+      Log.w(TAG, "RenderHandler.handleMessage: weak ref is null");
+      return;
+      }
+
+    switch (what)
+      {
+      case MSG_SURFACE_CREATED: renderThread.surfaceCreated();
+                                break;
+      case MSG_SURFACE_CHANGED: renderThread.surfaceChanged(msg.arg1, msg.arg2);
+                                break;
+      case MSG_DO_FRAME:        long timestamp = (((long) msg.arg1) << 32) | (((long) msg.arg2) & 0xffffffffL);
+                                renderThread.doFrame(timestamp);
+                                break;
+      case MSG_SHUTDOWN:        renderThread.shutdown();
+                                break;
+      default:                  throw new RuntimeException("unknown message " + what);
+      }
+    }
+  }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/examples/surfaceview/RenderThread.java b/src/main/java/org/distorted/examples/surfaceview/RenderThread.java
new file mode 100644
index 0000000..9fc19d2
--- /dev/null
+++ b/src/main/java/org/distorted/examples/surfaceview/RenderThread.java
@@ -0,0 +1,293 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.examples.surfaceview;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.opengl.EGL14;
+import android.opengl.EGLSurface;
+import android.opengl.GLES31;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import org.distorted.library.effect.MatrixEffectScale;
+import org.distorted.library.effect.VertexEffectDistort;
+import org.distorted.library.main.Distorted;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.library.mesh.MeshFlat;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.examples.R;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class RenderThread extends Thread
+  {
+  private static final String TAG = "RenderThread";
+
+  // Object must be created on render thread to get correct Looper, but is used from
+  // UI thread, so we need to declare it volatile to ensure the UI thread sees a fully
+  // constructed object.
+  private volatile RenderHandler mHandler;
+
+  // Used to wait for the thread to start.
+  private final Object mStartLock = new Object();
+  private boolean mReady = false;
+  private volatile SurfaceHolder mSurfaceHolder;  // may be updated by UI thread
+  private EglCore eglCore;
+  private EGLSurface eglSurface;
+
+  private DistortedEffects mEffects;
+  private DistortedTexture mTexture;
+  private MeshFlat mMesh;
+  private DistortedScreen mScreen;
+  private SurfaceView mView;
+  private static boolean resourcesCreated = false;
+  private Static3D mScale;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RenderThread(SurfaceHolder holder, SurfaceView view)
+    {
+    mSurfaceHolder = holder;
+    mView = view;
+
+    Static3D pLeft = new Static3D( 90, 108, 0);
+    Static3D pRight= new Static3D(176, 111, 0);
+
+    Static4D rLeft = new Static4D(-10, 10, 0,25);
+    Static4D rRight= new Static4D( 10,  5, 0,25);
+
+    Dynamic3D dLeft = new Dynamic3D(1000,0.0f);
+    Dynamic3D dRight= new Dynamic3D(1000,0.0f);
+
+    dLeft.add( new Static3D(  0,  0, 0) );
+    dLeft.add( new Static3D(-20, 20, 0) );
+
+    dRight.add( new Static3D(  0,  0, 0) );
+    dRight.add( new Static3D( 20, 10, 0) );
+
+    mEffects = new DistortedEffects();
+    mEffects.apply( new VertexEffectDistort(dLeft , pLeft , rLeft ) );
+    mEffects.apply( new VertexEffectDistort(dRight, pRight, rRight) );
+
+    mScale= new Static3D(1,1,1);
+    mEffects.apply(new MatrixEffectScale(mScale));
+
+    mScreen = new DistortedScreen();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void setResourcesCreated()
+    {
+    resourcesCreated = false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void checkGlError(String op)
+    {
+    int error = GLES31.glGetError();
+
+    if (error != GLES31.GL_NO_ERROR)
+      {
+      String msg = op + ": glError 0x" + Integer.toHexString(error);
+      Log.e(TAG, msg);
+//    throw new RuntimeException(msg);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void run()
+    {
+    Looper.prepare();
+    mHandler = new RenderHandler(this);
+    eglCore = new EglCore(null, EglCore.FLAG_RECORDABLE | EglCore.FLAG_TRY_GLES3);
+
+    synchronized (mStartLock)
+      {
+      mReady = true;
+      mStartLock.notify();    // signal waitUntilReady()
+      }
+
+    Looper.loop();
+    Log.d(TAG, "looper quit");
+
+    checkGlError("releaseGl start");
+
+    if (eglSurface != null)
+      {
+      eglCore.releaseSurface(eglSurface);
+      eglSurface = EGL14.EGL_NO_SURFACE;
+      }
+
+    checkGlError("releaseGl done");
+
+    eglCore.makeNothingCurrent();
+    eglCore.release();
+
+    synchronized (mStartLock)
+      {
+      mReady = false;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void waitUntilReady()
+    {
+    synchronized (mStartLock)
+      {
+      while (!mReady)
+        {
+        try
+          {
+          mStartLock.wait();
+          }
+        catch (InterruptedException ie) {  }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void shutdown()
+    {
+    Log.d(TAG, "shutdown");
+    Looper.myLooper().quit();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RenderHandler getHandler()
+    {
+    return mHandler;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void surfaceCreated()
+    {
+    Log.d(TAG, "surfaceCreated");
+
+    Surface surface = mSurfaceHolder.getSurface();
+
+    eglSurface = eglCore.createWindowSurface(surface);
+    eglCore.makeCurrent(eglSurface);
+
+    createResources();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void surfaceChanged(int width, int height)
+    {
+    Log.d(TAG, "surfaceChanged " + width + "x" + height);
+
+    float horiRatio = (float)width / mTexture.getWidth();
+    float vertRatio = (float)height/ mTexture.getHeight();
+    float factor    = horiRatio > vertRatio ? vertRatio : horiRatio;
+
+    mScale.set( factor,factor,factor );
+    mScreen.resize(width, height);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createResources()
+    {
+    resourcesCreated = true;
+
+    InputStream is = mView.getContext().getResources().openRawResource(R.raw.monalisa);
+    Bitmap bmp;
+
+    try
+      {
+      bmp = BitmapFactory.decodeStream(is);
+      }
+    finally
+      {
+      try
+        {
+        is.close();
+        }
+      catch(IOException io) {}
+      }
+
+    int bmpHeight = bmp.getHeight();
+    int bmpWidth  = bmp.getWidth();
+
+    if( mTexture==null ) mTexture = new DistortedTexture(bmpWidth,bmpHeight);
+    mTexture.setTexture(bmp);
+
+    if( mMesh==null ) mMesh = new MeshFlat(9,9*bmpHeight/bmpWidth);
+
+    mScreen.detachAll();
+    mScreen.attach(mTexture,mEffects,mMesh);
+
+    VertexEffectDistort.enable();
+
+    try
+      {
+      Distorted.onCreate(mView.getContext());
+      }
+    catch(Exception ex)
+      {
+      Log.e("PlainMonaLisa", ex.getMessage() );
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void doFrame(long frameTimeNs)
+    {
+    // We can still get here after onPaused - ignore such calls.
+    if( SurfaceViewSurfaceView.isPaused() )
+      {
+      android.util.Log.e("Thread", "Got here after onPaused- ignoring frame draw call!!");
+      return;
+      }
+
+    // This will happen if we just briefly went into the background (press 'POWER')
+    // Then surfaceCreated/changed is not called
+    if( !resourcesCreated )
+      {
+      Log.e("Thread", "resources not created!!");
+      createResources();
+      }
+
+    eglCore.makeCurrent(eglSurface);
+    mScreen.render( frameTimeNs/1000000 );  // 1 nanosecond = 1 millisecond / 10^-6
+    eglCore.swapBuffers(eglSurface);
+    }
+
+  }
diff --git a/src/main/java/org/distorted/examples/surfaceview/SurfaceViewActivity.java b/src/main/java/org/distorted/examples/surfaceview/SurfaceViewActivity.java
new file mode 100644
index 0000000..b29c955
--- /dev/null
+++ b/src/main/java/org/distorted/examples/surfaceview/SurfaceViewActivity.java
@@ -0,0 +1,71 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.examples.surfaceview;
+
+import org.distorted.library.main.Distorted;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SurfaceViewActivity extends Activity
+{
+    private SurfaceViewSurfaceView mView;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onCreate(Bundle icicle) 
+      {
+      super.onCreate(icicle);
+      mView = new SurfaceViewSurfaceView(this);
+      setContentView(mView);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onPause() 
+      {
+      mView.onPause();
+      Distorted.onPause();
+      super.onPause();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onResume() 
+      {
+      super.onResume();
+      mView.onResume();
+      }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onDestroy() 
+      {
+      Distorted.onDestroy();  
+      super.onDestroy();
+      }
+    
+}
diff --git a/src/main/java/org/distorted/examples/surfaceview/SurfaceViewSurfaceView.java b/src/main/java/org/distorted/examples/surfaceview/SurfaceViewSurfaceView.java
new file mode 100644
index 0000000..2de4610
--- /dev/null
+++ b/src/main/java/org/distorted/examples/surfaceview/SurfaceViewSurfaceView.java
@@ -0,0 +1,159 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.examples.surfaceview;
+
+import android.content.Context;
+import android.view.Choreographer;
+import android.view.SurfaceView;
+import android.view.SurfaceHolder;
+import android.util.Log;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class SurfaceViewSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Choreographer.FrameCallback
+  {
+  private static final String TAG = "MonaLisaSurface";
+  private RenderThread mRenderThread;
+  private static volatile boolean mPaused = true;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SurfaceViewSurfaceView(Context context)
+    {
+    super(context);
+    getHolder().addCallback(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void surfaceCreated(SurfaceHolder holder)
+    {
+    Log.d(TAG, "surfaceCreated holder=" + holder);
+
+    mRenderThread = new RenderThread(holder, this);
+    mRenderThread.setName("GL render");
+    mRenderThread.start();
+    mRenderThread.waitUntilReady();
+
+    RenderHandler rh = mRenderThread.getHandler();
+
+    if (rh != null)
+      {
+      rh.sendSurfaceCreated();
+      }
+
+    Choreographer.getInstance().postFrameCallback(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
+    {
+    Log.d(TAG, "surfaceChanged fmt=" + format + " size=" + width + "x" + height +" holder=" + holder);
+
+    RenderHandler rh = mRenderThread.getHandler();
+
+    if (rh != null)
+      {
+      rh.sendSurfaceChanged(format, width, height);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder holder)
+    {
+    Log.d(TAG, "surfaceDestroyed holder=" + holder);
+
+    // We need to wait for the render thread to shut down before continuing because we
+    // don't want the Surface to disappear out from under it mid-render.  The frame
+    // notifications will have been stopped back in onPause(), but there might have
+    // been one in progress.
+
+    RenderHandler rh = mRenderThread.getHandler();
+
+    if (rh != null)
+      {
+      rh.sendShutdown();
+
+      try
+        {
+        mRenderThread.join();
+        }
+      catch (InterruptedException ie)
+        {
+        throw new RuntimeException("join was interrupted", ie);
+        }
+      }
+    mRenderThread = null;
+
+    Log.d(TAG, "surfaceDestroyed complete");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onPause()
+    {
+    mPaused = true;
+    RenderThread.setResourcesCreated();
+
+    Log.d(TAG, "onPause unhooking choreographer");
+    Choreographer.getInstance().removeFrameCallback(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onResume()
+    {
+    mPaused = false;
+
+    if (mRenderThread != null)
+      {
+      Log.d(TAG, "onResume re-hooking choreographer");
+      Choreographer.getInstance().postFrameCallback(this);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static boolean isPaused()
+    {
+    return mPaused;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void doFrame(long frameTimeNanos)
+    {
+    RenderHandler rh = mRenderThread.getHandler();
+
+    if (rh != null)
+      {
+      Choreographer.getInstance().postFrameCallback(this);
+      rh.sendDoFrame(frameTimeNanos);
+      }
+    }
+  }
+
+
diff --git a/src/main/java/org/distorted/examples/wind/WindEffectsManager.java b/src/main/java/org/distorted/examples/wind/WindEffectsManager.java
index 332c5ed..437be7f 100644
--- a/src/main/java/org/distorted/examples/wind/WindEffectsManager.java
+++ b/src/main/java/org/distorted/examples/wind/WindEffectsManager.java
@@ -105,14 +105,14 @@ class WindEffectsManager
 
   void apply(DistortedEffects effects)
     {
-    Static3D midLeft    = new Static3D(0,mHeight/2,0);
-    Static3D midRight   = new Static3D(mWidth,mHeight/2,0);
-    Static3D tadRight   = new Static3D(3*mWidth/4,mHeight/2,0);
-    Static4D windRegion = new Static4D(0,0,0,mHeight);
+    Static3D midLeftMatrix= new Static3D(-mWidth/2,0,0);          // matrix effects have their origin in the center!
+    Static3D midRight     = new Static3D(mWidth,mHeight/2,0);     //
+    Static3D tadRight     = new Static3D(3*mWidth/4,mHeight/2,0); // whereas vertex effects - in the bottom-left corner
+    Static4D windRegion   = new Static4D(0,0,0,mHeight);          //
 
     setWind(0);
 
-    effects.apply( new MatrixEffectShear(shearFactor,midLeft) );
+    effects.apply( new MatrixEffectShear(shearFactor,midLeftMatrix) );
     effects.apply( new MatrixEffectScale(scaleFactor) );
     effects.apply( new VertexEffectDeform(deformForce,midRight) );
     effects.apply( new VertexEffectWave(windDynamic1, midRight, windRegion) );
diff --git a/src/main/java/org/distorted/examples/wind/WindRenderer.java b/src/main/java/org/distorted/examples/wind/WindRenderer.java
index 039509e..41a8d47 100644
--- a/src/main/java/org/distorted/examples/wind/WindRenderer.java
+++ b/src/main/java/org/distorted/examples/wind/WindRenderer.java
@@ -80,7 +80,7 @@ class WindRenderer implements GLSurfaceView.Renderer
 
       Static1D angle = new Static1D(-45);
       Static3D axis  = new Static3D(0,0,1);
-      Static3D center= new Static3D(0,mObjHeight/2,0);
+      Static3D center= new Static3D(-mObjWidth*0.5f,0,0);
 
       effects.apply( new MatrixEffectRotate(angle, axis, center) );
       mManager.apply(effects);
@@ -112,7 +112,7 @@ class WindRenderer implements GLSurfaceView.Renderer
       int min = width<height? width:height;
 
       float factor = ((float)min)/(mObjHeight + 1.4f*mObjWidth);
-      mMove.set( factor*mObjHeight*0.58f +(width-min)/2, height - factor*mObjHeight*1.08f -(height-min)/2, 0 );
+      mMove.set( factor*mObjHeight*1.28f -min*0.5f, min*0.5f - factor*mObjHeight*0.58f, 0 );
       mScale.set(factor,factor,factor);
       mScreen.resize(width, height);
       }
