From 0d668e056657c62c58b33917e62fd9790a4b5f3c Mon Sep 17 00:00:00 2001 From: Nicolas CASTEL Date: Wed, 23 Apr 2014 00:51:39 +0200 Subject: [PATCH 01/23] switch base layer to jpeg format by default --- WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml | 3 ++- WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml b/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml index f5120c2..3521d7b 100644 --- a/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml +++ b/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml @@ -19,10 +19,11 @@ Earth/BMNGWMS/BMNG(Shaded + Bathymetry) Tiled - Version 1.1 - 5.2004 image/png + image/jpeg image/png image/dds - .png + .jpg diff --git a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml index bd2f448..37cf9ab 100644 --- a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml +++ b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml @@ -10,12 +10,13 @@ 26 03 2009 00:00:00 GMT Earth/NASA LandSat I3 WMS - image/png + image/jpeg + image/jpeg image/png image/dds - .png + .jpg From 63cbbdbfefa6d4101904c4bfcfd540f7c668baa2 Mon Sep 17 00:00:00 2001 From: Nicolas CASTEL Date: Wed, 23 Apr 2014 00:57:21 +0200 Subject: [PATCH 02/23] update Manifest to claim uses of ETC texture compression --- WorldWindAndroid/AndroidManifest.xml | 3 +++ WorldWindAndroid/project.properties | 2 +- WorldWindowApplicationSample/AndroidManifest.xml | 11 ++++++++--- WorldWindowApplicationSample/project.properties | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/WorldWindAndroid/AndroidManifest.xml b/WorldWindAndroid/AndroidManifest.xml index c8da25c..6ccee9c 100644 --- a/WorldWindAndroid/AndroidManifest.xml +++ b/WorldWindAndroid/AndroidManifest.xml @@ -10,6 +10,9 @@ + + + diff --git a/WorldWindAndroid/project.properties b/WorldWindAndroid/project.properties index 36f1594..1b8c5a3 100644 --- a/WorldWindAndroid/project.properties +++ b/WorldWindAndroid/project.properties @@ -11,5 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-15 +target=android-18 android.library=true diff --git a/WorldWindowApplicationSample/AndroidManifest.xml b/WorldWindowApplicationSample/AndroidManifest.xml index 932775f..17c26bc 100644 --- a/WorldWindowApplicationSample/AndroidManifest.xml +++ b/WorldWindowApplicationSample/AndroidManifest.xml @@ -8,14 +8,19 @@ --> + android:versionCode="6" + android:versionName="1.0" > + + android:targetSdkVersion="18" /> + + + + diff --git a/WorldWindowApplicationSample/project.properties b/WorldWindowApplicationSample/project.properties index fddb90b..a0502e4 100644 --- a/WorldWindowApplicationSample/project.properties +++ b/WorldWindowApplicationSample/project.properties @@ -11,5 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-15 +target=android-18 android.library.reference.1=../WorldWindAndroid From 5e4fb6856f6968db81763cdcf16a97fbaa2dabb4 Mon Sep 17 00:00:00 2001 From: Nicolas CASTEL Date: Wed, 23 Apr 2014 00:58:56 +0200 Subject: [PATCH 03/23] Update Manifest to claim requirement of Opengl ES 2.0 --- WorldWindAndroid/AndroidManifest.xml | 2 +- WorldWindowApplicationSample/AndroidManifest.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WorldWindAndroid/AndroidManifest.xml b/WorldWindAndroid/AndroidManifest.xml index 6ccee9c..0ee1dce 100644 --- a/WorldWindAndroid/AndroidManifest.xml +++ b/WorldWindAndroid/AndroidManifest.xml @@ -9,7 +9,7 @@ - + diff --git a/WorldWindowApplicationSample/AndroidManifest.xml b/WorldWindowApplicationSample/AndroidManifest.xml index 17c26bc..b89b449 100644 --- a/WorldWindowApplicationSample/AndroidManifest.xml +++ b/WorldWindowApplicationSample/AndroidManifest.xml @@ -16,7 +16,7 @@ android:minSdkVersion="15" android:targetSdkVersion="18" /> - + From 954ab50b23df7b5c576809431ce5987426b9164c Mon Sep 17 00:00:00 2001 From: Nicolas CASTEL Date: Wed, 23 Apr 2014 01:52:54 +0200 Subject: [PATCH 04/23] Add code to check ETC/DXTC support at startup Compatibility whith OPENGL ES 3.0 when available --- .../worldwind/WorldWindowGLSurfaceView.java | 50 +++++++++++++- .../worldwind/util/pkm/PKMGpuTextureData.java | 67 ++++++++++++------- 2 files changed, 92 insertions(+), 25 deletions(-) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/WorldWindowGLSurfaceView.java b/WorldWindAndroid/src/gov/nasa/worldwind/WorldWindowGLSurfaceView.java index 42b8ada..cc8e95f 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/WorldWindowGLSurfaceView.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/WorldWindowGLSurfaceView.java @@ -17,9 +17,14 @@ import gov.nasa.worldwind.geom.Position; import gov.nasa.worldwind.pick.*; import gov.nasa.worldwind.util.Logging; +import gov.nasa.worldwind.util.pkm.PKMGpuTextureData; +import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.opengles.GL10; + import java.beans.*; import java.util.*; @@ -27,8 +32,11 @@ * @author dcollins * @version $Id: WorldWindowGLSurfaceView.java 831 2012-10-08 20:51:39Z tgaskins $ */ -public class WorldWindowGLSurfaceView extends GLSurfaceView implements GLSurfaceView.Renderer, WorldWindow, WWObject +public class WorldWindowGLSurfaceView extends GLSurfaceView implements GLSurfaceView.EGLContextFactory, GLSurfaceView.Renderer, WorldWindow, WWObject { + protected static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + protected static double glVersion = 3.0; + protected WWObjectImpl wwo = new WWObjectImpl(this); protected SceneController sceneController; protected InputHandler inputHandler; @@ -42,7 +50,9 @@ public class WorldWindowGLSurfaceView extends GLSurfaceView implements GLSurface public WorldWindowGLSurfaceView(Context context) { super(context); - + + this.setEGLContextFactory(this); + try { this.init(null); @@ -58,6 +68,8 @@ public WorldWindowGLSurfaceView(Context context) public WorldWindowGLSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); + + this.setEGLContextFactory(this); try { @@ -75,6 +87,8 @@ public WorldWindowGLSurfaceView(Context context, EGLConfigChooser configChooser) { super(context); + this.setEGLContextFactory(this); + try { this.init(configChooser); @@ -162,6 +176,8 @@ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) // recognize. if (this.gpuResourceCache != null) this.gpuResourceCache.clear(); + + PKMGpuTextureData.initTCSupport(); } @Override @@ -503,4 +519,34 @@ public void onMessage(Message message) { // Empty implementation } + + + + @Override + public EGLContext createContext( + EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { + Logging.warning("creating OpenGL ES " + glVersion + " context"); + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, (int) glVersion, + EGL10.EGL_NONE }; + // attempt to create a OpenGL ES 3.0 context + EGLContext context = egl.eglCreateContext( + display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); + if(context == null) { + Logging.warning("OpenGL ES " + glVersion + " context not supported"); + glVersion = 2.0; + Logging.warning("creating OpenGL ES " + glVersion + " context"); + int[] attrib_list2 = {EGL_CONTEXT_CLIENT_VERSION, (int) glVersion, + EGL10.EGL_NONE }; + // create a OpenGL ES 2.0 context + context = egl.eglCreateContext( + display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list2); + } + + return context; // returns null if 3.0 is not supported; + } + + @Override + public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { + egl.eglDestroyContext(display, context); + } } diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java index ac08047..9ce1c14 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java @@ -5,18 +5,21 @@ */ package gov.nasa.worldwind.util.pkm; +import gov.nasa.worldwind.render.GpuTextureData; +import gov.nasa.worldwind.util.Logging; +import gov.nasa.worldwind.util.WWIO; +import gov.nasa.worldwind.util.WWUtil; +import gov.nasa.worldwind.util.dds.DDSTextureReader; + import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.opengl.ETC1Util; import android.opengl.ETC1Util.ETC1Texture; -import gov.nasa.worldwind.render.GpuTextureData; -import gov.nasa.worldwind.util.Logging; -import gov.nasa.worldwind.util.WWIO; -import gov.nasa.worldwind.util.WWUtil; -import gov.nasa.worldwind.util.dds.DDSTextureReader; +import android.opengl.GLES20; /** * @author dcollins @@ -24,9 +27,23 @@ */ public class PKMGpuTextureData extends GpuTextureData { + protected static boolean isETC1Supported; + protected static boolean isDXTSupported; + protected ETC1Texture etcCompressedData; - public static PKMGpuTextureData fromETCCompressedData(ETC1Texture etctex, long estimatedMemorySize) + public static void initTCSupport() { + isETC1Supported = ETC1Util.isETC1Supported(); + isDXTSupported = isDXTSupported(); + } + + private static boolean isDXTSupported() { + String extensions = GLES20.glGetString(GLES20.GL_EXTENSIONS); + Logging.warning(extensions); + return extensions.contains("GL_EXT_texture_compression_s3tc"); + } + + public static PKMGpuTextureData fromETCCompressedData(ETC1Texture etctex, long estimatedMemorySize) { if (etctex == null || etctex.getHeight() == 0) { @@ -112,23 +129,27 @@ protected static GpuTextureData fromStream(InputStream stream) GpuTextureData data = null; try { -// stream.mark(DEFAULT_MARK_LIMIT); -// -// DDSTextureReader ddsReader = new DDSTextureReader(); -// data = ddsReader.read(stream); -// if (data != null) -// return data; -// -// stream.reset(); - - stream.mark(DEFAULT_MARK_LIMIT); - - PKMReader pkmReader = new PKMReader(); - data = pkmReader.read(stream); - if (data != null) - return data; - - stream.reset(); + if(isDXTSupported) { + stream.mark(DEFAULT_MARK_LIMIT); + + DDSTextureReader ddsReader = new DDSTextureReader(); + data = ddsReader.read(stream); + if (data != null) + return data; + + stream.reset(); + } + + if(isETC1Supported) { + stream.mark(DEFAULT_MARK_LIMIT); + + PKMReader pkmReader = new PKMReader(); + data = pkmReader.read(stream); + if (data != null) + return data; + + stream.reset(); + } Bitmap bitmap = BitmapFactory.decodeStream(stream); return bitmap != null ? new GpuTextureData(bitmap, estimateMemorySize(bitmap)) : null; From 1be26c7bc24a25052068a7362ede2b89852f9427 Mon Sep 17 00:00:00 2001 From: Nicolas CASTEL Date: Wed, 23 Apr 2014 01:57:39 +0200 Subject: [PATCH 05/23] modify defqult view altitude in order to avoid starting on a black screen --- .../src/nicastel/android/ww/WorldWindowActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java b/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java index b0e17ce..076bb7e 100644 --- a/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java +++ b/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java @@ -59,7 +59,7 @@ public class WorldWindowActivity extends Activity { private final static double BOLZANO_LONGITUDE = 11.3254d; private final static double BOLZANO_VIEW_HEADING = 60d; private final static double BOLZANO_VIEW_TILT = 60d; - private final static double BOLZANO_VIEW_DISTANCE_KM = 13000d; + private final static double BOLZANO_VIEW_DISTANCE_KM = 1300000d; protected WorldWindowGLSurfaceView wwd; From b9c2a0b0077cb39d447f04db51c2dccef75cdac8 Mon Sep 17 00:00:00 2001 From: Nicolas CASTEL Date: Wed, 23 Apr 2014 02:00:29 +0200 Subject: [PATCH 06/23] add an empty string.xml file to avoid compilation problems after checkout --- WorldWindAndroid/res/values/strings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 WorldWindAndroid/res/values/strings.xml diff --git a/WorldWindAndroid/res/values/strings.xml b/WorldWindAndroid/res/values/strings.xml new file mode 100644 index 0000000..b6db954 --- /dev/null +++ b/WorldWindAndroid/res/values/strings.xml @@ -0,0 +1,8 @@ + + + + From dfd985575cdfb9b25f1a2cc0206ea1f94b0c60c4 Mon Sep 17 00:00:00 2001 From: Nicolas CASTEL Date: Wed, 23 Apr 2014 17:25:01 +0200 Subject: [PATCH 07/23] Revert changes for OPENGL ES 3.0 compatibility, no change needed --- .../worldwind/WorldWindowGLSurfaceView.java | 49 ++++--------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/WorldWindowGLSurfaceView.java b/WorldWindAndroid/src/gov/nasa/worldwind/WorldWindowGLSurfaceView.java index cc8e95f..f62422e 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/WorldWindowGLSurfaceView.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/WorldWindowGLSurfaceView.java @@ -32,10 +32,9 @@ * @author dcollins * @version $Id: WorldWindowGLSurfaceView.java 831 2012-10-08 20:51:39Z tgaskins $ */ -public class WorldWindowGLSurfaceView extends GLSurfaceView implements GLSurfaceView.EGLContextFactory, GLSurfaceView.Renderer, WorldWindow, WWObject +public class WorldWindowGLSurfaceView extends GLSurfaceView implements GLSurfaceView.Renderer, WorldWindow, WWObject { - protected static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - protected static double glVersion = 3.0; + protected static String glVersion; protected WWObjectImpl wwo = new WWObjectImpl(this); protected SceneController sceneController; @@ -51,8 +50,6 @@ public WorldWindowGLSurfaceView(Context context) { super(context); - this.setEGLContextFactory(this); - try { this.init(null); @@ -68,8 +65,6 @@ public WorldWindowGLSurfaceView(Context context) public WorldWindowGLSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); - - this.setEGLContextFactory(this); try { @@ -86,8 +81,6 @@ public WorldWindowGLSurfaceView(Context context, AttributeSet attrs) public WorldWindowGLSurfaceView(Context context, EGLConfigChooser configChooser) { super(context); - - this.setEGLContextFactory(this); try { @@ -177,10 +170,16 @@ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) if (this.gpuResourceCache != null) this.gpuResourceCache.clear(); + initGlVersion(); PKMGpuTextureData.initTCSupport(); } - @Override + private void initGlVersion() { + glVersion = GLES20.glGetString(GLES20.GL_VERSION); + Logging.warning("GL version : "+glVersion); + } + + @Override public boolean onTouchEvent(MotionEvent event) { // Let the InputHandler process the touch event first. If it returns true indicating that it handled the event, @@ -519,34 +518,4 @@ public void onMessage(Message message) { // Empty implementation } - - - - @Override - public EGLContext createContext( - EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { - Logging.warning("creating OpenGL ES " + glVersion + " context"); - int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, (int) glVersion, - EGL10.EGL_NONE }; - // attempt to create a OpenGL ES 3.0 context - EGLContext context = egl.eglCreateContext( - display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); - if(context == null) { - Logging.warning("OpenGL ES " + glVersion + " context not supported"); - glVersion = 2.0; - Logging.warning("creating OpenGL ES " + glVersion + " context"); - int[] attrib_list2 = {EGL_CONTEXT_CLIENT_VERSION, (int) glVersion, - EGL10.EGL_NONE }; - // create a OpenGL ES 2.0 context - context = egl.eglCreateContext( - display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list2); - } - - return context; // returns null if 3.0 is not supported; - } - - @Override - public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { - egl.eglDestroyContext(display, context); - } } From f54252cf4301e9ef6a84b3a6b273299f1fcec6ae Mon Sep 17 00:00:00 2001 From: Nicolas CASTEL Date: Thu, 24 Apr 2014 00:00:29 +0200 Subject: [PATCH 08/23] hide the status bar --- .../src/nicastel/android/ww/WorldWindowActivity.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java b/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java index 076bb7e..39b99a1 100644 --- a/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java +++ b/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java @@ -25,20 +25,20 @@ import gov.nasa.worldwind.globes.Globe; import gov.nasa.worldwind.layers.Layer; import gov.nasa.worldwind.layers.LayerList; -import nicastel.android.ww.R; import java.io.File; import java.io.IOException; import java.util.List; import nicastel.android.ww.dialogs.AddWMSDialog; -import nicastel.android.ww.dialogs.TocDialog; import nicastel.android.ww.dialogs.AddWMSDialog.OnAddWMSLayersListener; +import nicastel.android.ww.dialogs.TocDialog; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import android.view.WindowManager; import android.widget.TextView; /** @@ -101,6 +101,11 @@ public void onCreate(Bundle savedInstanceState) { // set the contentview this.setContentView(R.layout.main); + + // In order to hide the status bar / Full screen mode + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN); + + // And initialize the WorldWindow Model and View this.wwd = (WorldWindowGLSurfaceView) this.findViewById(R.id.wwd); this.wwd.setModel((Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME)); From 3e7e783574c9359ec129b5d334273452301f4125 Mon Sep 17 00:00:00 2001 From: Nicolas CASTEL Date: Thu, 24 Apr 2014 00:31:27 +0200 Subject: [PATCH 09/23] Full screen mode without navigation bar --- .../AndroidManifest.xml | 2 +- WorldWindowApplicationSample/project.properties | 2 +- .../android/ww/WorldWindowActivity.java | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/WorldWindowApplicationSample/AndroidManifest.xml b/WorldWindowApplicationSample/AndroidManifest.xml index b89b449..4977ef1 100644 --- a/WorldWindowApplicationSample/AndroidManifest.xml +++ b/WorldWindowApplicationSample/AndroidManifest.xml @@ -14,7 +14,7 @@ + android:targetSdkVersion="19" /> diff --git a/WorldWindowApplicationSample/project.properties b/WorldWindowApplicationSample/project.properties index a0502e4..13c87d0 100644 --- a/WorldWindowApplicationSample/project.properties +++ b/WorldWindowApplicationSample/project.properties @@ -11,5 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-18 +target=android-19 android.library.reference.1=../WorldWindAndroid diff --git a/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java b/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java index 39b99a1..6f88779 100644 --- a/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java +++ b/WorldWindowApplicationSample/src/nicastel/android/ww/WorldWindowActivity.java @@ -38,6 +38,7 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.view.WindowManager; import android.widget.TextView; @@ -104,7 +105,10 @@ public void onCreate(Bundle savedInstanceState) { // In order to hide the status bar / Full screen mode getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN); - + this.getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); // And initialize the WorldWindow Model and View this.wwd = (WorldWindowGLSurfaceView) this.findViewById(R.id.wwd); @@ -293,4 +297,15 @@ private void showLayerManager() { tocDialog.setWorldWindData(this.wwd); tocDialog.show(getFragmentManager(), "tocDialog"); } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + this.getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + } } From 78c3ffc841eb00dbf804169c62a44df5d1887912 Mon Sep 17 00:00:00 2001 From: Nicolas CASTEL Date: Thu, 24 Apr 2014 00:58:00 +0200 Subject: [PATCH 10/23] identify function that can be offloaded to another thread --- .../src/gov/nasa/worldwind/render/GpuTextureTile.java | 3 ++- .../src/gov/nasa/worldwind/terrain/TiledTessellator.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureTile.java b/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureTile.java index 87a00aa..2df6987 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureTile.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureTile.java @@ -190,7 +190,7 @@ public void applyInternalTransform(DrawContext dc, Matrix matrix) { } protected GpuTexture getOrCreateTexture(DrawContext dc) { - if (this.textureData != null) { + if (this.textureData != null) { GpuTexture texture = this.createTexture(dc, this.textureData); if (texture != null) this.setTexture(dc.getGpuResourceCache(), texture); else { @@ -203,6 +203,7 @@ protected GpuTexture getOrCreateTexture(DrawContext dc) { } protected GpuTexture createTexture(DrawContext dc, GpuTextureData textureData) { + // TODO : offload texture creation to another thread return GpuTexture.createTexture(dc, textureData); } diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/terrain/TiledTessellator.java b/WorldWindAndroid/src/gov/nasa/worldwind/terrain/TiledTessellator.java index 28bf102..0cf4ac7 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/terrain/TiledTessellator.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/terrain/TiledTessellator.java @@ -1170,6 +1170,7 @@ protected void renderOutline(DrawContext dc, TerrainTile tile) { } protected void loadGeometryVbos(DrawContext dc, TerrainGeometry geom) { + // TODO : offload vbo creation to another thread // Load the terrain geometry into the GpuResourceCache. GpuResourceCache cache = dc.getGpuResourceCache(); int[] vboIds = (int[]) cache.get(geom.vboCacheKey); @@ -1198,6 +1199,7 @@ protected void loadGeometryVbos(DrawContext dc, TerrainGeometry geom) { } protected void loadSharedGeometryVBOs(DrawContext dc, TerrainSharedGeometry geom) { + // TODO : offload vbo creation to another thread GpuResourceCache cache = dc.getGpuResourceCache(); int[] vboIds = (int[]) cache.get(geom.vboCacheKey); if (vboIds != null) return; From ee0a4bf5957790955391f4ccd9d91aac1099e2cb Mon Sep 17 00:00:00 2001 From: nicastel Date: Wed, 14 May 2014 22:22:02 +0200 Subject: [PATCH 11/23] add suuport for DDS file with ETC1 compression --- .../gov/nasa/worldwind/util/dds/DDSConstants.java | 2 ++ .../nasa/worldwind/util/dds/DDSTextureReader.java | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSConstants.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSConstants.java index 4a116b7..d140ec7 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSConstants.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSConstants.java @@ -36,6 +36,8 @@ public class DDSConstants public static final int D3DFMT_DXT3 = makeFourCC('D', 'X', 'T', '3'); public static final int D3DFMT_DXT4 = makeFourCC('D', 'X', 'T', '4'); public static final int D3DFMT_DXT5 = makeFourCC('D', 'X', 'T', '5'); + + public static final int D3DFMT_ETC1 = makeFourCC('E', 'T', 'C', '1'); // A DWORD (magic number) containing the four character code value 'DDS ' (0x20534444) public static final int MAGIC = makeFourCC('D', 'D', 'S', ' '); diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSTextureReader.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSTextureReader.java index f805600..dcfb12b 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSTextureReader.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSTextureReader.java @@ -6,9 +6,11 @@ package gov.nasa.worldwind.util.dds; import gov.nasa.worldwind.render.GpuTextureData; -import gov.nasa.worldwind.util.*; +import gov.nasa.worldwind.util.Logging; +import gov.nasa.worldwind.util.WWIO; -import java.io.*; +import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; /** @@ -22,6 +24,9 @@ public class DDSTextureReader protected static final int GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1; protected static final int GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; protected static final int GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3; + + // ETC1 compression internal formats. See http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt. + protected static final int GL_ETC1_RGB8_OES = 0x8D64; public DDSTextureReader() { @@ -114,6 +119,9 @@ else if (fourcc == DDSConstants.D3DFMT_DXT2 || fourcc == DDSConstants.D3DFMT_DXT else if (fourcc == DDSConstants.D3DFMT_DXT4 || fourcc == DDSConstants.D3DFMT_DXT5) return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + + else if (fourcc == DDSConstants.D3DFMT_ETC1) + return GL_ETC1_RGB8_OES; return 0; } @@ -128,7 +136,7 @@ protected int getImageSize(DDSHeader header, int width, int height) int fourcc = header.getPixelFormat().getFourCC(); - if (fourcc == DDSConstants.D3DFMT_DXT1) + if (fourcc == DDSConstants.D3DFMT_DXT1 || fourcc == DDSConstants.D3DFMT_ETC1) return (minWidth * minHeight) / 2; else if (fourcc == DDSConstants.D3DFMT_DXT2 || fourcc == DDSConstants.D3DFMT_DXT3) From 6e551b284f878f0934796013f0c6ed3c61f2628e Mon Sep 17 00:00:00 2001 From: nicastel Date: Wed, 14 May 2014 22:23:18 +0200 Subject: [PATCH 12/23] refactoring --- .../gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java | 2 +- .../util/pkm/{PKMReader.java => PKMTextureReader.java} | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) rename WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/{PKMReader.java => PKMTextureReader.java} (82%) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java index 9ce1c14..5c4dc04 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java @@ -143,7 +143,7 @@ protected static GpuTextureData fromStream(InputStream stream) if(isETC1Supported) { stream.mark(DEFAULT_MARK_LIMIT); - PKMReader pkmReader = new PKMReader(); + PKMTextureReader pkmReader = new PKMTextureReader(); data = pkmReader.read(stream); if (data != null) return data; diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMReader.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java similarity index 82% rename from WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMReader.java rename to WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java index 2422a3d..7fb9fb7 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMReader.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java @@ -4,10 +4,11 @@ import java.io.InputStream; import android.opengl.ETC1; +//import android.opengl.ETC1; import android.opengl.ETC1Util; import android.opengl.ETC1Util.ETC1Texture; -public class PKMReader { +public class PKMTextureReader { public PKMGpuTextureData read(InputStream stream) throws IOException { try { @@ -23,12 +24,11 @@ public PKMGpuTextureData read(InputStream stream) throws IOException { return null; } } catch (IOException e) { - //stream.reset(); - //texture = encodeTexture(stream); +// stream.reset(); +// return encodeTexture(stream); e.printStackTrace(); System.out.println("Texture PKM failed "); return null; } } - } From d6cad91d3d61a30cd7adf0991d57e42d7a7239f5 Mon Sep 17 00:00:00 2001 From: nicastel Date: Wed, 14 May 2014 22:29:27 +0200 Subject: [PATCH 13/23] refactoring --- .../src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java | 2 +- .../src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java index 5c4dc04..677f83e 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java @@ -43,7 +43,7 @@ private static boolean isDXTSupported() { return extensions.contains("GL_EXT_texture_compression_s3tc"); } - public static PKMGpuTextureData fromETCCompressedData(ETC1Texture etctex, long estimatedMemorySize) + public static PKMGpuTextureData fromPKMETC1CompressedData(ETC1Texture etctex, long estimatedMemorySize) { if (etctex == null || etctex.getHeight() == 0) { diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java index 7fb9fb7..a32759b 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java @@ -18,7 +18,7 @@ public PKMGpuTextureData read(InputStream stream) throws IOException { if (texture != null) { int estimatedMemorySize = ETC1.ETC_PKM_HEADER_SIZE + texture.getHeight() * texture.getWidth() / 2; - return PKMGpuTextureData.fromETCCompressedData(texture, + return PKMGpuTextureData.fromPKMETC1CompressedData(texture, estimatedMemorySize); } else { return null; From 2c2a9067de06cf94f7db68e5c55db0c889c7caa8 Mon Sep 17 00:00:00 2001 From: nicastel Date: Wed, 14 May 2014 22:31:39 +0200 Subject: [PATCH 14/23] correct support for non pkm surface image --- .../src/gov/nasa/worldwind/render/SurfaceImage.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/render/SurfaceImage.java b/WorldWindAndroid/src/gov/nasa/worldwind/render/SurfaceImage.java index 9dff5e5..0203413 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/render/SurfaceImage.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/render/SurfaceImage.java @@ -132,6 +132,10 @@ protected GpuTexture loadGpuTexture(DrawContext dc) GpuTexture texture = null; GpuTextureData textureData = PKMGpuTextureData.createTextureData(this.imagePath); + if (textureData == null) + { + GpuTextureData.createTextureData(this.imagePath); + } if (textureData != null) { texture = GpuTexture.createTexture(dc, textureData); From eb8732e4c006f2c3f1ce4b2cb9e53d20a0882af9 Mon Sep 17 00:00:00 2001 From: nicastel Date: Wed, 14 May 2014 22:33:47 +0200 Subject: [PATCH 15/23] correct bug --- .../src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java index 677f83e..60a5d10 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java @@ -129,7 +129,7 @@ protected static GpuTextureData fromStream(InputStream stream) GpuTextureData data = null; try { - if(isDXTSupported) { + if(isDXTSupported || isETC1Supported) { stream.mark(DEFAULT_MARK_LIMIT); DDSTextureReader ddsReader = new DDSTextureReader(); From d2992d52eb1b0b55d9d5ee61e5e8ef926de029ce Mon Sep 17 00:00:00 2001 From: nicastel Date: Wed, 14 May 2014 23:03:42 +0200 Subject: [PATCH 16/23] optimize bitmap decoding to use RGB 565 when available --- .../nasa/worldwind/render/GpuTextureData.java | 23 +++++++++++++++---- .../worldwind/util/pkm/PKMGpuTextureData.java | 7 +++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureData.java b/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureData.java index 4540014..b20d661 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureData.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureData.java @@ -5,15 +5,25 @@ */ package gov.nasa.worldwind.render; -import android.graphics.*; -import android.opengl.*; import gov.nasa.worldwind.cache.Cacheable; -import gov.nasa.worldwind.util.*; +import gov.nasa.worldwind.util.Logging; +import gov.nasa.worldwind.util.WWIO; +import gov.nasa.worldwind.util.WWUtil; import gov.nasa.worldwind.util.dds.DDSTextureReader; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory; +import android.graphics.BitmapFactory.Options; +import android.opengl.GLES20; +import android.opengl.GLUtils; + + /** * @author dcollins * @version $Id: GpuTextureData.java 764 2012-09-11 00:17:17Z tgaskins $ @@ -157,7 +167,10 @@ protected static GpuTextureData fromStream(InputStream stream) stream.reset(); - Bitmap bitmap = BitmapFactory.decodeStream(stream); + Options opts = new BitmapFactory.Options(); + opts.inPreferQualityOverSpeed = false; + opts.inPreferredConfig = Config.RGB_565; + Bitmap bitmap = BitmapFactory.decodeStream(stream, null, opts); return bitmap != null ? new GpuTextureData(bitmap, estimateMemorySize(bitmap)) : null; } catch (IOException e) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java index 60a5d10..4594d87 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java @@ -17,6 +17,8 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory.Options; import android.opengl.ETC1Util; import android.opengl.ETC1Util.ETC1Texture; import android.opengl.GLES20; @@ -151,7 +153,10 @@ protected static GpuTextureData fromStream(InputStream stream) stream.reset(); } - Bitmap bitmap = BitmapFactory.decodeStream(stream); + Options opts = new BitmapFactory.Options(); + opts.inPreferredConfig = Config.RGB_565; + + Bitmap bitmap = BitmapFactory.decodeStream(stream, null, opts); return bitmap != null ? new GpuTextureData(bitmap, estimateMemorySize(bitmap)) : null; } catch (IOException e) From 2d76e012e56ac73d87e5327a8f5e8e3b17565091 Mon Sep 17 00:00:00 2001 From: nicastel Date: Sat, 17 May 2014 01:23:20 +0200 Subject: [PATCH 17/23] Integrates handling of server side dds with ETC1 compressed layer Experimental real time texture compression using Renderscript (generated cache are OK but stability is affected) --- WorldWindAndroid/AndroidManifest.xml | 7 +- WorldWindAndroid/project.properties | 5 +- .../src/config/Earth/BMNGWMSLayer.xml | 4 +- .../src/config/Earth/LandsatI3WMSLayer.xml | 2 +- .../gov/nasa/worldwind/render/GpuTexture.java | 2 +- .../nasa/worldwind/render/GpuTextureData.java | 18 +- .../AbstractRetrievalPostProcessor.java | 10 +- .../gov/nasa/worldwind/util/ImageUtil.java | 1 + .../worldwind/util/dds/DDSCompressor.java | 23 +- .../worldwind/util/dds/ETC1Compressor.java | 112 ++++ .../worldwind/util/pkm/PKMGpuTextureData.java | 4 +- .../worldwind/util/pkm/PKMTextureReader.java | 7 +- .../etc1/rs/RsETC1.java | 222 +++++++ .../etc1/rs/RsETC1Util.java | 246 +++++++ .../etc1/rs/etc1compressor.rs | 617 ++++++++++++++++++ .../project.properties | 5 + .../src/config/Earth/GeoserverEtc1Test.xml | 51 ++ .../src/config/worldwind.layers2.xml | 8 +- .../android/ww/WorldWindowActivity.java | 15 + .../src/renderscript/etc1compressor.rs | 617 ++++++++++++++++++ 20 files changed, 1949 insertions(+), 27 deletions(-) create mode 100644 WorldWindAndroid/src/gov/nasa/worldwind/util/dds/ETC1Compressor.java create mode 100644 WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java create mode 100644 WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java create mode 100644 WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs create mode 100644 WorldWindowApplicationSample/src/config/Earth/GeoserverEtc1Test.xml create mode 100644 WorldWindowApplicationSample/src/renderscript/etc1compressor.rs diff --git a/WorldWindAndroid/AndroidManifest.xml b/WorldWindAndroid/AndroidManifest.xml index 0ee1dce..1fe2db2 100644 --- a/WorldWindAndroid/AndroidManifest.xml +++ b/WorldWindAndroid/AndroidManifest.xml @@ -3,11 +3,12 @@ package="gov.nasa.worldwind" android:versionCode="1" android:versionName="1.0" > + + - - + android:targetSdkVersion="19" /> + diff --git a/WorldWindAndroid/project.properties b/WorldWindAndroid/project.properties index 1b8c5a3..bcbfcc6 100644 --- a/WorldWindAndroid/project.properties +++ b/WorldWindAndroid/project.properties @@ -11,5 +11,8 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-18 +target=android-19 android.library=true +renderscript.target=19 +renderscript.support.mode=true +sdk.buildtools=19.0.3 diff --git a/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml b/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml index 3521d7b..f232020 100644 --- a/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml +++ b/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml @@ -17,13 +17,13 @@ 26 03 2009 00:00:00 GMT Earth/BMNGWMS/BMNG(Shaded + Bathymetry) Tiled - Version 1.1 - 5.2004 - image/png + image/jpeg image/jpeg image/png image/dds - .jpg + .dds diff --git a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml index 37cf9ab..394a69e 100644 --- a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml +++ b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml @@ -16,7 +16,7 @@ image/png image/dds - .jpg + .dds diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTexture.java b/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTexture.java index 829c387..c3cc50f 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTexture.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTexture.java @@ -10,11 +10,11 @@ import gov.nasa.worldwind.geom.Matrix; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.pkm.PKMGpuTextureData; +import nicastel.renderscripttexturecompressor.etc1.rs.RsETC1Util.ETC1Texture; import android.graphics.Bitmap; import android.opengl.ETC1; import android.opengl.GLES20; import android.opengl.GLUtils; -import android.opengl.ETC1Util.ETC1Texture; /** * Edited By: Nicola Dorigatti, Trilogis diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureData.java b/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureData.java index b20d661..aa7dfe6 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureData.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/render/GpuTextureData.java @@ -152,6 +152,9 @@ public static GpuTextureData createTextureData(Object source) } protected static final int DEFAULT_MARK_LIMIT = 1024; + +// public static RenderScript rs; +// public static ScriptC_etc1compressor script; protected static GpuTextureData fromStream(InputStream stream) { @@ -165,12 +168,25 @@ protected static GpuTextureData fromStream(InputStream stream) if (data != null) return data; - stream.reset(); + stream.reset(); Options opts = new BitmapFactory.Options(); opts.inPreferQualityOverSpeed = false; opts.inPreferredConfig = Config.RGB_565; Bitmap bitmap = BitmapFactory.decodeStream(stream, null, opts); +// +// if(bitmap != null) { +// ETC1Texture texture = RsETC1Util.compressBitmap(rs, script, bitmap); +// if (texture != null) { +// int estimatedMemorySize = ETC1.ETC_PKM_HEADER_SIZE +// + texture.getHeight() * texture.getWidth() / 2; +// return PKMGpuTextureData.fromPKMETC1CompressedData(texture, +// estimatedMemorySize); +// } else { +// return null; +// } +// } + return bitmap != null ? new GpuTextureData(bitmap, estimateMemorySize(bitmap)) : null; } catch (IOException e) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/retrieve/AbstractRetrievalPostProcessor.java b/WorldWindAndroid/src/gov/nasa/worldwind/retrieve/AbstractRetrievalPostProcessor.java index 20c53ea..3cb5abf 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/retrieve/AbstractRetrievalPostProcessor.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/retrieve/AbstractRetrievalPostProcessor.java @@ -264,6 +264,7 @@ protected File getOutputFile() */ protected boolean overwriteExistingFile() { + // TODO return false; } @@ -635,10 +636,11 @@ protected ByteBuffer convertToDDS() throws IOException { ByteBuffer buffer; - Bitmap image = this.transformPixels(); - if (image != null) - buffer = DDSCompressor.compressImage(image); - else + // TODO +// Bitmap image = this.transformPixels(); +// if (image != null) +// buffer = DDSCompressor.compressImage(image); +// else buffer = DDSCompressor.compressImageBuffer(this.getRetriever().getBuffer()); return buffer; diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/ImageUtil.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/ImageUtil.java index ca7e504..df5b37d 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/ImageUtil.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/ImageUtil.java @@ -137,6 +137,7 @@ public static Bitmap bitmapFromByteBuffer(ByteBuffer imageBuffer) return BitmapFactory.decodeStream(inputStream); } + // TODO : what is this method for ?? public static Bitmap mapTransparencyColors(Bitmap sourceImage, int[] originalColors) { if (sourceImage == null) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSCompressor.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSCompressor.java index d94be7e..3477dcc 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSCompressor.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSCompressor.java @@ -12,6 +12,8 @@ import gov.nasa.worldwind.util.WWMath; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory.Options; /** * DDSCompressor converts in-memory images into a DDS file encoded with one of the DXT block compression algorithms. If @@ -58,8 +60,12 @@ public static java.nio.ByteBuffer compressImageStream(java.io.InputStream inputS Logging.error(message); throw new IllegalArgumentException(message); } - - Bitmap image = BitmapFactory.decodeStream(inputStream); + + Options opts = new BitmapFactory.Options(); + //opts.inPreferQualityOverSpeed = false; + opts.inPreferredConfig = Config.RGB_565; + Bitmap image = BitmapFactory.decodeStream(inputStream, null, opts); + if (image == null) { return null; } @@ -191,9 +197,12 @@ public static java.nio.ByteBuffer compressImage(Bitmap image) { */ public static DXTCompressionAttributes getDefaultCompressionAttributes() { DXTCompressionAttributes attributes = new DXTCompressionAttributes(); + + // TODO bug with 2*2 image but bug with worldwind dds reader if there is no mimmap attributes.setBuildMipmaps(true); // Always build mipmaps. + attributes.setPremultiplyAlpha(true); // Always create premultiplied alpha format files.. - attributes.setDXTFormat(0); // Allow the DDSCompressor to choose the appropriate DXT format. + attributes.setDXTFormat(DDSConstants.D3DFMT_ETC1); // Allow the DDSCompressor to choose the appropriate DXT format. return attributes; } @@ -347,12 +356,14 @@ protected DXTCompressor getDXTCompressor(Bitmap image, DXTCompressionAttributes // Otherwise, we choose one automatically from the image type. If no choice can be made from the image type, // we default to using a DXT3 compressor. - if (attributes.getDXTFormat() == DDSConstants.D3DFMT_DXT1) { + if (attributes.getDXTFormat() == DDSConstants.D3DFMT_ETC1) { + return new ETC1Compressor(); + } else if (attributes.getDXTFormat() == DDSConstants.D3DFMT_DXT1) { return new DXT1Compressor(); } else if (attributes.getDXTFormat() == DDSConstants.D3DFMT_DXT2 || attributes.getDXTFormat() == DDSConstants.D3DFMT_DXT3) { return new DXT3Compressor(); } else if (!image.hasAlpha()) { - return new DXT1Compressor(); + return new ETC1Compressor(); } else { return new DXT3Compressor(); } @@ -374,7 +385,7 @@ protected Bitmap[] buildMipMaps(Bitmap image, DXTCompressionAttributes attribute // data is accessed directly. In this case, such code would be responsible for recognizing the color model // (premultiplied) and behaving accordingly. - Bitmap.Config mipmapImageType = Bitmap.Config.ARGB_8888; + Bitmap.Config mipmapImageType = Bitmap.Config.RGB_565; int maxLevel = ImageUtil.getMaxMipmapLevel(image.getWidth(), image.getHeight()); return ImageUtil.buildMipmaps(image, mipmapImageType, maxLevel); diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/ETC1Compressor.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/ETC1Compressor.java new file mode 100644 index 0000000..f261e60 --- /dev/null +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/ETC1Compressor.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 United States Government as represented by the Administrator of the + * National Aeronautics and Space Administration. + * All Rights Reserved. + */ + +package gov.nasa.worldwind.util.dds; + +import gov.nasa.worldwind.util.Logging; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import nicastel.renderscripttexturecompressor.etc1.rs.RsETC1; +import nicastel.renderscripttexturecompressor.etc1.rs.ScriptC_etc1compressor; +import android.graphics.Bitmap; +import android.support.v8.renderscript.RenderScript; + +/** + * @author nicastel + * @author dcollins + * @version $Id: DXT1Compressor.java 733 2012-09-02 17:15:09Z dcollins $ + */ +public class ETC1Compressor implements DXTCompressor +{ + public ETC1Compressor() + { + } + + public int getDXTFormat() + { + return DDSConstants.D3DFMT_ETC1; + } + + public int getCompressedSize(Bitmap image, DXTCompressionAttributes attributes) + { + if (image == null) + { + String message = Logging.getMessage("nullValue.ImageIsNull"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + if (attributes == null) + { + String message = Logging.getMessage("nullValue.AttributesIsNull"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + + // TODO: comment, provide documentation reference + + int width = Math.max(image.getWidth(), 4); + int height = Math.max(image.getHeight(), 4); + + return (width * height) / 2; + } + + public static RenderScript rs; + public static ScriptC_etc1compressor script; + + public void compressImage(Bitmap image, DXTCompressionAttributes attributes, + java.nio.ByteBuffer buffer) + { + if (image == null) + { + String message = Logging.getMessage("nullValue.ImageIsNull"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + if (attributes == null) + { + String message = Logging.getMessage("nullValue.AttributesIsNull"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + if (buffer == null) + { + String message = Logging.getMessage("nullValue.BufferNull"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + + // TODO + int width = Math.max(image.getWidth(), 4); + int height = Math.max(image.getHeight(), 4); + + int encodedImageSize = RsETC1.getEncodedDataSize(width, height); + System.out.println("encodedImageSize : "+encodedImageSize); + + // TODO + ByteBuffer bufferIn = ByteBuffer.allocateDirect( + image.getRowBytes() * image.getHeight()).order( + ByteOrder.nativeOrder()); + image.copyPixelsToBuffer(bufferIn); + bufferIn.rewind(); + + ByteBuffer bufferOut = ByteBuffer.allocateDirect(encodedImageSize); + + + + RsETC1.encodeImage(rs, script, bufferIn, image.getWidth(), image.getHeight(), image.getRowBytes()/image.getWidth(), image.getRowBytes(), bufferOut); + + bufferOut.rewind(); + + buffer.put(bufferOut); + } + + protected ColorBlockExtractor getColorBlockExtractor(Bitmap image) + { + return new BasicColorBlockExtractor(image); + } +} diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java index 4594d87..1603381 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java @@ -15,12 +15,12 @@ import java.io.IOException; import java.io.InputStream; +import nicastel.renderscripttexturecompressor.etc1.rs.RsETC1Util.ETC1Texture; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.opengl.ETC1Util; -import android.opengl.ETC1Util.ETC1Texture; import android.opengl.GLES20; /** diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java index a32759b..35f414c 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java @@ -3,17 +3,16 @@ import java.io.IOException; import java.io.InputStream; +import nicastel.renderscripttexturecompressor.etc1.rs.RsETC1Util; +import nicastel.renderscripttexturecompressor.etc1.rs.RsETC1Util.ETC1Texture; import android.opengl.ETC1; -//import android.opengl.ETC1; -import android.opengl.ETC1Util; -import android.opengl.ETC1Util.ETC1Texture; public class PKMTextureReader { public PKMGpuTextureData read(InputStream stream) throws IOException { try { stream.mark(1024); - ETC1Texture texture = ETC1Util.createTexture(stream); + ETC1Texture texture = RsETC1Util.createTexture(stream); if (texture != null) { int estimatedMemorySize = ETC1.ETC_PKM_HEADER_SIZE diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java new file mode 100644 index 0000000..2efc4c8 --- /dev/null +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java @@ -0,0 +1,222 @@ +package nicastel.renderscripttexturecompressor.etc1.rs; + +import java.nio.ByteBuffer; + +import nicastel.renderscripttexturecompressor.etc1.rs.ScriptC_etc1compressor; + +import android.support.v8.renderscript.RenderScript; +import android.support.v8.renderscript.Allocation; +import android.support.v8.renderscript.Element; + +public class RsETC1 { + // Copyright 2009 Google Inc. + // 2011 Nicolas CASTEL + // + // Licensed under the Apache License, Version 2.0 (the "License"); + // you may not use this file except in compliance with the License. + // You may obtain a copy of the License at + // + // http://www.apache.org/licenses/LICENSE-2.0 + // + // Unless required by applicable law or agreed to in writing, software + // distributed under the License is distributed on an "AS IS" BASIS, + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + // See the License for the specific language governing permissions and + // limitations under the License. + + /** + * Size in bytes of an encoded block. + */ + public static final int ENCODED_BLOCK_SIZE = 8; + + /** + * Size in bytes of a decoded block. + */ + public static final int DECODED_BLOCK_SIZE = 48; + + /** + * Size of a PKM file header, in bytes. + */ + public static final int ETC_PKM_HEADER_SIZE = 16; + + /** + * Accepted by the internalformat parameter of glCompressedTexImage2D. + */ + public static final int ETC1_RGB8_OES = 0x8D64; + + short etc1_byte; + int etc1_bool; + /* unsigned */long etc1_uint32; + + + static short convert4To8(int b) { + int c = b & 0xf; + return (short) ((c << 4) | c); + } + + static short convert4To8(long b) { + long c = b & 0xf; + return (short) ((c << 4) | c); + } + + static short convert5To8(int b) { + int c = b & 0x1f; + return (short) ((c << 3) | (c >> 2)); + } + + static short convert5To8(long b) { + long c = b & 0x1f; + return (short) ((c << 3) | (c >> 2)); + } + + static short convert6To8(int b) { + int c = b & 0x3f; + return (short) ((c << 2) | (c >> 4)); + } + + static short convert6To8(long b) { + long c = b & 0x3f; + return (short) ((c << 2) | (c >> 4)); + } + + /** + * Return the size of the encoded image data (does not include size of PKM + * header). + */ + public static int getEncodedDataSize(int width, int height) { + return (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1; + } + + /** + * Encode an entire image. pIn - pointer to the image data. Formatted such + * that the Red component of pixel (x,y) is at pIn + pixelSize * x + stride + * * y + redOffset; pOut - pointer to encoded data. Must be large enough to + * store entire encoded image. + * @param script + */ + public static int encodeImage(RenderScript rs, ScriptC_etc1compressor script, ByteBuffer pIn, int width, int height, + int pixelSize, int stride, ByteBuffer compressedImage) { + + long tInitArray = java.lang.System.currentTimeMillis(); + + script.set_height(height); + script.set_width(width); + + if (pixelSize < 2 || pixelSize > 3) { + System.out.println("unsupported pixelSize"); + return -1; + } + + // int iOut = 0; + + int size = width * height / (DECODED_BLOCK_SIZE / 3); + + Allocation p00 = Allocation.createSized(rs, Element.U8(rs), width * height * pixelSize); + Allocation aout = Allocation.createSized(rs, Element.U16_4(rs), size); + + tInitArray = java.lang.System.currentTimeMillis() - tInitArray; + System.out.println("tInitArray : "+tInitArray+" ms"); + + long tFillAlloc = java.lang.System.currentTimeMillis(); + p00.copyFrom(pIn.array()); + + script.bind_pInA(p00); + + tFillAlloc = java.lang.System.currentTimeMillis() - tFillAlloc; + System.out.println("tFillAlloc : "+tFillAlloc+" ms"); + + long tExec = java.lang.System.currentTimeMillis(); + script.forEach_root(aout); + tExec = java.lang.System.currentTimeMillis() - tExec; + System.out.println("tExec : "+tExec+" ms"); + + long tFillOut = java.lang.System.currentTimeMillis(); + short[] arrayOut3Temp = new short[4*size]; + aout.copyTo(arrayOut3Temp); + + Allocation aout2 = Allocation.createSized(rs, Element.U8(rs), 8*size); + aout2.copyFromUnchecked(arrayOut3Temp); + + aout2.copyTo(compressedImage.array()); + aout2.destroy(); + + tFillOut = java.lang.System.currentTimeMillis() - tFillOut; + System.out.println("tFillOut : "+tFillOut+" ms"); + + compressedImage.position(0); + return 0; + } + + static final byte kMagic[] = { 'P', 'K', 'M', ' ', '1', '0' }; + + static final int ETC1_PKM_FORMAT_OFFSET = 6; + static final int ETC1_PKM_ENCODED_WIDTH_OFFSET = 8; + static final int ETC1_PKM_ENCODED_HEIGHT_OFFSET = 10; + static final int ETC1_PKM_WIDTH_OFFSET = 12; + static final int ETC1_PKM_HEIGHT_OFFSET = 14; + + static final int ETC1_RGB_NO_MIPMAPS = 0; + + static void writeBEUint16(ByteBuffer header, int iOut, int data) { + header.position(iOut); + header.put((byte) (data >> 8)); + header.put((byte) data); + } + + static int readBEUint16(ByteBuffer headerBuffer, int iIn) { + return (headerBuffer.get(iIn) << 8) | headerBuffer.get(iIn + 1); + } + + // Format a PKM header + + public static void formatHeader(ByteBuffer header, int width, int height) { + header.put(kMagic); + int encodedWidth = (width + 3) & ~3; + int encodedHeight = (height + 3) & ~3; + writeBEUint16(header, ETC1_PKM_FORMAT_OFFSET, ETC1_RGB_NO_MIPMAPS); + writeBEUint16(header, ETC1_PKM_ENCODED_WIDTH_OFFSET, encodedWidth); + writeBEUint16(header, ETC1_PKM_ENCODED_HEIGHT_OFFSET, encodedHeight); + writeBEUint16(header, ETC1_PKM_WIDTH_OFFSET, width); + writeBEUint16(header, ETC1_PKM_HEIGHT_OFFSET, height); + } + + // Check if a PKM header is correctly formatted. + + public static boolean isValid(ByteBuffer headerBuffer) { + if (memcmp(headerBuffer, kMagic, kMagic.length)) { + return false; + } + int format = readBEUint16(headerBuffer, ETC1_PKM_FORMAT_OFFSET); + int encodedWidth = readBEUint16(headerBuffer, + ETC1_PKM_ENCODED_WIDTH_OFFSET); + int encodedHeight = readBEUint16(headerBuffer, + ETC1_PKM_ENCODED_HEIGHT_OFFSET); + int width = readBEUint16(headerBuffer, ETC1_PKM_WIDTH_OFFSET); + int height = readBEUint16(headerBuffer, ETC1_PKM_HEIGHT_OFFSET); + return format == ETC1_RGB_NO_MIPMAPS && encodedWidth >= width + && encodedWidth - width < 4 && encodedHeight >= height + && encodedHeight - height < 4; + } + + static boolean memcmp(ByteBuffer headerBuffer, byte[] b, int lenght) { + for (int i = 0; i < lenght; i++) { + if (headerBuffer.get(i) != b[i]) { + return true; + } + } + return false; + } + + // Read the image width from a PKM header + + public static int getWidth(ByteBuffer pHeader) { + return readBEUint16(pHeader, ETC1_PKM_WIDTH_OFFSET); + } + + // Read the image height from a PKM header + + public static int getHeight(ByteBuffer pHeader) { + return readBEUint16(pHeader, ETC1_PKM_HEIGHT_OFFSET); + } + +} diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java new file mode 100644 index 0000000..b3bfece --- /dev/null +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java @@ -0,0 +1,246 @@ +package nicastel.renderscripttexturecompressor.etc1.rs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import nicastel.renderscripttexturecompressor.etc1.rs.ScriptC_etc1compressor; +import android.graphics.Bitmap; +import android.opengl.ETC1; +import android.opengl.GLES10; +import android.support.v8.renderscript.RenderScript; + +/** + * Utility methods for using ETC1 compressed textures. + * + */ +public class RsETC1Util { + /** + * Convenience method to load an ETC1 texture whether or not the active OpenGL context + * supports the ETC1 texture compression format. + * @param target the texture target. + * @param level the texture level + * @param border the border size. Typically 0. + * @param fallbackFormat the format to use if ETC1 texture compression is not supported. + * Must be GL_RGB. + * @param fallbackType the type to use if ETC1 texture compression is not supported. + * Can be either GL_UNSIGNED_SHORT_5_6_5, which implies 16-bits-per-pixel, + * or GL_UNSIGNED_BYTE, which implies 24-bits-per-pixel. + * @param input the input stream containing an ETC1 texture in PKM format. + * @throws IOException + */ + public static void loadTexture(int target, int level, int border, + int fallbackFormat, int fallbackType, InputStream input) + throws IOException { + loadTexture(target, level, border, fallbackFormat, fallbackType, createTexture(input)); + } + + /** + * Convenience method to load an ETC1 texture whether or not the active OpenGL context + * supports the ETC1 texture compression format. + * @param target the texture target. + * @param level the texture level + * @param border the border size. Typically 0. + * @param fallbackFormat the format to use if ETC1 texture compression is not supported. + * Must be GL_RGB. + * @param fallbackType the type to use if ETC1 texture compression is not supported. + * Can be either GL_UNSIGNED_SHORT_5_6_5, which implies 16-bits-per-pixel, + * or GL_UNSIGNED_BYTE, which implies 24-bits-per-pixel. + * @param texture the ETC1 to load. + */ + public static void loadTexture(int target, int level, int border, + int fallbackFormat, int fallbackType, ETC1Texture texture) { + if (fallbackFormat != GLES10.GL_RGB) { + throw new IllegalArgumentException("fallbackFormat must be GL_RGB"); + } + if (! (fallbackType == GLES10.GL_UNSIGNED_SHORT_5_6_5 + || fallbackType == GLES10.GL_UNSIGNED_BYTE)) { + throw new IllegalArgumentException("Unsupported fallbackType"); + } + + int width = texture.getWidth(); + int height = texture.getHeight(); + Buffer data = texture.getData(); + if (isETC1Supported()) { + int imageSize = data.remaining(); + GLES10.glCompressedTexImage2D(target, level, RsETC1.ETC1_RGB8_OES, width, height, + border, imageSize, data); + } else { + boolean useShorts = fallbackType != GLES10.GL_UNSIGNED_BYTE; + int pixelSize = useShorts ? 2 : 3; + int stride = pixelSize * width; + ByteBuffer decodedData = ByteBuffer.allocateDirect(stride*height) + .order(ByteOrder.nativeOrder()); + ETC1.decodeImage((ByteBuffer) data, decodedData, width, height, pixelSize, stride); + GLES10.glTexImage2D(target, level, fallbackFormat, width, height, border, + fallbackFormat, fallbackType, decodedData); + } + } + + /** + * Check if ETC1 texture compression is supported by the active OpenGL ES context. + * @return true if the active OpenGL ES context supports ETC1 texture compression. + */ + public static boolean isETC1Supported() { + int[] results = new int[20]; + GLES10.glGetIntegerv(GLES10.GL_NUM_COMPRESSED_TEXTURE_FORMATS, results, 0); + int numFormats = results[0]; + if (numFormats > results.length) { + results = new int[numFormats]; + } + GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, results, 0); + for (int i = 0; i < numFormats; i++) { + if (results[i] == RsETC1.ETC1_RGB8_OES) { + return true; + } + } + return false; + } + + /** + * A utility class encapsulating a compressed ETC1 texture. + */ + public static class ETC1Texture { + public ETC1Texture(int width, int height, ByteBuffer data) { + mWidth = width; + mHeight = height; + mData = data; + } + + /** + * Get the width of the texture in pixels. + * @return the width of the texture in pixels. + */ + public int getWidth() { return mWidth; } + + /** + * Get the height of the texture in pixels. + * @return the width of the texture in pixels. + */ + public int getHeight() { return mHeight; } + + /** + * Get the compressed data of the texture. + * @return the texture data. + */ + public ByteBuffer getData() { return mData; } + + private int mWidth; + private int mHeight; + private ByteBuffer mData; + } + + /** + * Create a new ETC1Texture from an input stream containing a PKM formatted compressed texture. + * @param input an input stream containing a PKM formatted compressed texture. + * @return an ETC1Texture read from the input stream. + * @throws IOException + */ + public static ETC1Texture createTexture(InputStream input) throws IOException { + int width = 0; + int height = 0; + byte[] ioBuffer = new byte[4096]; + { + if (input.read(ioBuffer, 0, RsETC1.ETC_PKM_HEADER_SIZE) != RsETC1.ETC_PKM_HEADER_SIZE) { + throw new IOException("Unable to read PKM file header."); + } + ByteBuffer headerBuffer = ByteBuffer.allocateDirect(RsETC1.ETC_PKM_HEADER_SIZE) + .order(ByteOrder.nativeOrder()); + headerBuffer.put(ioBuffer, 0, RsETC1.ETC_PKM_HEADER_SIZE).position(0); + if (!RsETC1.isValid(headerBuffer)) { + throw new IOException("Not a PKM file."); + } + width = RsETC1.getWidth(headerBuffer); + height = RsETC1.getHeight(headerBuffer); + } + int encodedSize = RsETC1.getEncodedDataSize(width, height); + ByteBuffer dataBuffer = ByteBuffer.allocateDirect(encodedSize).order(ByteOrder.nativeOrder()); + for (int i = 0; i < encodedSize; ) { + int chunkSize = Math.min(ioBuffer.length, encodedSize - i); + if (input.read(ioBuffer, 0, chunkSize) != chunkSize) { + throw new IOException("Unable to read PKM file data."); + } + dataBuffer.put(ioBuffer, 0, chunkSize); + i += chunkSize; + } + dataBuffer.position(0); + return new ETC1Texture(width, height, dataBuffer); + } + + /** + * Helper function that compresses an image into an ETC1Texture. + * @param bitmap + * @return the ETC1 texture. + */ + public static ETC1Texture compressBitmap(RenderScript rs, ScriptC_etc1compressor script, Bitmap bitmap){ + int encodedImageSize = RsETC1.getEncodedDataSize(bitmap.getWidth(), bitmap.getHeight()); + System.out.println("encodedImageSize : "+encodedImageSize); + ByteBuffer compressedImage = ByteBuffer.allocateDirect(encodedImageSize). + order(ByteOrder.nativeOrder()); + + ByteBuffer buffer = ByteBuffer.allocateDirect( + bitmap.getRowBytes() * bitmap.getHeight()).order( + ByteOrder.nativeOrder()); + bitmap.copyPixelsToBuffer(buffer); + buffer.rewind(); + + RsETC1.encodeImage(rs, script, buffer, bitmap.getWidth(), bitmap.getHeight(), bitmap.getByteCount(), bitmap.getByteCount() * bitmap.getWidth(), compressedImage); + + compressedImage.rewind(); + return new ETC1Texture(bitmap.getWidth(), bitmap.getHeight(), compressedImage); + } + + /** + * Helper function that compresses an image into an ETC1Texture. + * @param input a native order direct buffer containing the image data + * @param width the width of the image in pixels + * @param height the height of the image in pixels + * @param pixelSize the size of a pixel in bytes (2 or 3) + * @param stride the width of a line of the image in bytes + * @return the ETC1 texture. + */ + public static ETC1Texture compressTexture(RenderScript rs, ScriptC_etc1compressor script, Buffer input, int width, int height, int pixelSize, int stride){ + int encodedImageSize = RsETC1.getEncodedDataSize(width, height); + System.out.println("encodedImageSize : "+encodedImageSize); + ByteBuffer compressedImage = ByteBuffer.allocateDirect(encodedImageSize). + order(ByteOrder.nativeOrder()); + + RsETC1.encodeImage(rs, script, (ByteBuffer) input, width, height, pixelSize, stride, compressedImage); + + compressedImage.position(0); + return new ETC1Texture(width, height, compressedImage); + } + + /** + * Helper function that writes an ETC1Texture to an output stream formatted as a PKM file. + * @param texture the input texture. + * @param output the stream to write the formatted texture data to. + * @throws IOException + */ + public static void writeTexture(ETC1Texture texture, OutputStream output) throws IOException { + ByteBuffer dataBuffer = texture.getData(); + dataBuffer.rewind(); + System.out.println(dataBuffer.remaining()); + int originalPosition = dataBuffer.position(); + try { + int width = texture.getWidth(); + int height = texture.getHeight(); + ByteBuffer header = ByteBuffer.allocateDirect(RsETC1.ETC_PKM_HEADER_SIZE).order(ByteOrder.nativeOrder()); + RsETC1.formatHeader(header, width, height); + header.position(0); + byte[] ioBuffer = new byte[4096]; + header.get(ioBuffer, 0, RsETC1.ETC_PKM_HEADER_SIZE); + output.write(ioBuffer, 0, RsETC1.ETC_PKM_HEADER_SIZE); + while (dataBuffer.remaining()>0) { + int chunkSize = Math.min(ioBuffer.length, dataBuffer.remaining()); + dataBuffer.get(ioBuffer, 0, chunkSize); + output.write(ioBuffer, 0, chunkSize); + } + } finally { + dataBuffer.position(originalPosition); + } + } +} diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs new file mode 100644 index 0000000..214b0f8 --- /dev/null +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma version(1) +#pragma rs_fp_imprecise +#pragma rs java_package_name(nicastel.renderscripttexturecompressor.etc1.rs) + + +// DXT compressor copied here since I found no way to import properly +static inline uint8_t AV_RL16(const uint8_t * x) { + return ((((const uint8_t*)(x))[1] << 8) | + ((const uint8_t*)(x))[0]); +} + +static inline uint8_t AV_RL32(const uint8_t * x) { + return ((((const uint8_t*)(x))[3] << 24) | + (((const uint8_t*)(x))[2] << 16) | + (((const uint8_t*)(x))[1] << 8) | + ((const uint8_t*)(x))[0]); +} + +static inline uint8_t AV_RL64(const uint8_t * x) { + return (((uint64_t)((const uint8_t*)(x))[7] << 56) | + ((uint64_t)((const uint8_t*)(x))[6] << 48) | + ((uint64_t)((const uint8_t*)(x))[5] << 40) | + ((uint64_t)((const uint8_t*)(x))[4] << 32) | + ((uint64_t)((const uint8_t*)(x))[3] << 24) | + ((uint64_t)((const uint8_t*)(x))[2] << 16) | + ((uint64_t)((const uint8_t*)(x))[1] << 8) | + (uint64_t)((const uint8_t*)(x))[0]); +} + +static inline void dxt1_decode_pixels(const uint8_t *s, uint32_t *d, + unsigned int qstride, unsigned int flag,uint64_t alpha) { + unsigned int x, y, c0, c1, a = (!flag * 255) << 24; + unsigned int rb0, rb1, rb2, rb3, g0, g1, g2, g3; + uint32_t colors[4], pixels; + + c0 = AV_RL16(s); + c1 = AV_RL16(s+2); + + rb0 = (c0<<3 | c0<<8) & 0xf800f8; + rb1 = (c1<<3 | c1<<8) & 0xf800f8; + rb0 += (rb0>>5) & 0x070007; + rb1 += (rb1>>5) & 0x070007; + g0 = (c0 <<5) & 0x00fc00; + g1 = (c1 <<5) & 0x00fc00; + g0 += (g0 >>6) & 0x000300; + g1 += (g1 >>6) & 0x000300; + + colors[0] = rb0 + g0 + a; + colors[1] = rb1 + g1 + a; + + if (c0 > c1 || flag) { + rb2 = (((2*rb0+rb1) * 21) >> 6) & 0xff00ff; + rb3 = (((2*rb1+rb0) * 21) >> 6) & 0xff00ff; + g2 = (((2*g0 +g1 ) * 21) >> 6) & 0x00ff00; + g3 = (((2*g1 +g0 ) * 21) >> 6) & 0x00ff00; + colors[3] = rb3 + g3 + a; + } else { + rb2 = ((rb0+rb1) >> 1) & 0xff00ff; + g2 = ((g0 +g1 ) >> 1) & 0x00ff00; + colors[3] = 0; + } + + colors[2] = rb2 + g2 + a; + + pixels = AV_RL32(s+4); + for (y=0; y<4; y++) { + for (x=0; x<4; x++) { + a = (alpha & 0x0f) << 28; + a += a >> 4; + d[x] = a + colors[pixels&3]; + pixels >>= 2; + alpha >>= 4; + } + d += qstride; + } + } + +typedef unsigned char etc1_byte; +typedef int etc1_bool; +typedef unsigned int etc1_uint32; + +typedef struct EtcCompressed { + etc1_uint32 high; + etc1_uint32 low; + etc1_uint32 score; // Lower is more accurate +} etc_compressed; + +/* From http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt + + The number of bits that represent a 4x4 texel block is 64 bits if + is given by ETC1_RGB8_OES. + + The data for a block is a number of bytes, + + {q0, q1, q2, q3, q4, q5, q6, q7} + + where byte q0 is located at the lowest memory address and q7 at + the highest. The 64 bits specifying the block is then represented + by the following 64 bit integer: + + int64bit = 256*(256*(256*(256*(256*(256*(256*q0+q1)+q2)+q3)+q4)+q5)+q6)+q7; + + ETC1_RGB8_OES: + + a) bit layout in bits 63 through 32 if diffbit = 0 + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 + ----------------------------------------------- + | base col1 | base col2 | base col1 | base col2 | + | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| + ----------------------------------------------- + + 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + --------------------------------------------------- + | base col1 | base col2 | table | table |diff|flip| + | B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + --------------------------------------------------- + + + b) bit layout in bits 63 through 32 if diffbit = 1 + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 + ----------------------------------------------- + | base col1 | dcol 2 | base col1 | dcol 2 | + | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | + ----------------------------------------------- + + 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + --------------------------------------------------- + | base col 1 | dcol 2 | table | table |diff|flip| + | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + --------------------------------------------------- + + + c) bit layout in bits 31 through 0 (in both cases) + + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 + ----------------------------------------------- + | most significant pixel index bits | + | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| + ----------------------------------------------- + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + -------------------------------------------------- + | least significant pixel index bits | + | p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + -------------------------------------------------- + + + Add table 3.17.2: Intensity modifier sets for ETC1 compressed textures: + + table codeword modifier table + ------------------ ---------------------- + 0 -8 -2 2 8 + 1 -17 -5 5 17 + 2 -29 -9 9 29 + 3 -42 -13 13 42 + 4 -60 -18 18 60 + 5 -80 -24 24 80 + 6 -106 -33 33 106 + 7 -183 -47 47 183 + + + Add table 3.17.3 Mapping from pixel index values to modifier values for + ETC1 compressed textures: + + pixel index value + --------------- + msb lsb resulting modifier value + ----- ----- ------------------------- + 1 1 -b (large negative value) + 1 0 -a (small negative value) + 0 0 a (small positive value) + 0 1 b (large positive value) + + + */ + +static const int kModifierTable[] = { +/* 0 */2, 8, -2, -8, +/* 1 */5, 17, -5, -17, +/* 2 */9, 29, -9, -29, +/* 3 */13, 42, -13, -42, +/* 4 */18, 60, -18, -60, +/* 5 */24, 80, -24, -80, +/* 6 */33, 106, -33, -106, +/* 7 */47, 183, -47, -183 }; + +static const int kLookup[8] = { 0, 1, 2, 3, -4, -3, -2, -1 }; + +static inline etc1_byte etc1_clamp(int x) { + return (etc1_byte) (x >= 0 ? (x < 255 ? x : 255) : 0); +} + +static +inline int convert4To8(int b) { + int c = b & 0xf; + return (c << 4) | c; +} + +static +inline int convert5To8(int b) { + int c = b & 0x1f; + return (c << 3) | (c >> 2); +} + +static +inline int convert6To8(int b) { + int c = b & 0x3f; + return (c << 2) | (c >> 4); +} + +static +inline int divideBy255(int d) { + return (d + 128 + (d >> 8)) >> 8; +} + +static +inline int convert8To4(int b) { + int c = b & 0xff; + return divideBy255(c * 15); +} + +static +inline int convert8To5(int b) { + int c = b & 0xff; + return divideBy255(c * 31); +} + +static +inline int convertDiff(int base, int diff) { + return convert5To8((0x1f & base) + kLookup[0x7 & diff]); +} + +static +inline void take_best(etc_compressed* a, const etc_compressed* b) { + if (a->score > b->score) { + *a = *b; + } +} + +static void writeBigEndian(etc1_byte* pOut, etc1_uint32 d) { + pOut[0] = (etc1_byte)(d >> 24); + pOut[1] = (etc1_byte)(d >> 16); + pOut[2] = (etc1_byte)(d >> 8); + pOut[3] = (etc1_byte) d; +} + +static bool inRange4bitSigned(int color) { + return color >= -4 && color <= 3; +} + +static +inline int square(int x) { + return x * x; +} + +static +void etc_average_colors_subblock(const etc1_byte* pIn, etc1_uint32 inMask, etc1_byte* pColors, bool flipped, bool second) { + int r = 0; + int g = 0; + int b = 0; + + if (flipped) { + int by = 0; + if (second) { + by = 2; + } + for (int y = 0; y < 2; y++) { + int yy = by + y; + for (int x = 0; x < 4; x++) { + int i = x + 4 * yy; + if (inMask & (1 << i)) { + const etc1_byte* p = pIn + i * 3; + r += *(p++); + g += *(p++); + b += *(p++); + } + } + } + } else { + int bx = 0; + if (second) { + bx = 2; + } + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 2; x++) { + int xx = bx + x; + int i = xx + 4 * y; + if (inMask & (1 << i)) { + const etc1_byte* p = pIn + i * 3; + r += *(p++); + g += *(p++); + b += *(p++); + } + } + } + } + pColors[0] = (etc1_byte)((r + 4) >> 3); + pColors[1] = (etc1_byte)((g + 4) >> 3); + pColors[2] = (etc1_byte)((b + 4) >> 3); +} + +static +void etc_encodeBaseColors(etc1_byte* pBaseColors, const etc1_byte* pColors, etc_compressed* pCompressed) { + int r1, g1, b1, r2, g2, b2; // 8 bit base colors for sub-blocks + bool differential; + { + int r51 = convert8To5(pColors[0]); + int g51 = convert8To5(pColors[1]); + int b51 = convert8To5(pColors[2]); + int r52 = convert8To5(pColors[3]); + int g52 = convert8To5(pColors[4]); + int b52 = convert8To5(pColors[5]); + + r1 = convert5To8(r51); + g1 = convert5To8(g51); + b1 = convert5To8(b51); + + int dr = r52 - r51; + int dg = g52 - g51; + int db = b52 - b51; + + differential = inRange4bitSigned(dr) && inRange4bitSigned(dg) + && inRange4bitSigned(db); + if (differential) { + r2 = convert5To8(r51 + dr); + g2 = convert5To8(g51 + dg); + b2 = convert5To8(b51 + db); + pCompressed->high |= (r51 << 27) | ((7 & dr) << 24) | (g51 << 19) + | ((7 & dg) << 16) | (b51 << 11) | ((7 & db) << 8) | 2; + } + } + + if (!differential) { + int r41 = convert8To4(pColors[0]); + int g41 = convert8To4(pColors[1]); + int b41 = convert8To4(pColors[2]); + int r42 = convert8To4(pColors[3]); + int g42 = convert8To4(pColors[4]); + int b42 = convert8To4(pColors[5]); + r1 = convert4To8(r41); + g1 = convert4To8(g41); + b1 = convert4To8(b41); + r2 = convert4To8(r42); + g2 = convert4To8(g42); + b2 = convert4To8(b42); + pCompressed->high |= (r41 << 28) | (r42 << 24) | (g41 << 20) | (g42 + << 16) | (b41 << 12) | (b42 << 8); + } + pBaseColors[0] = r1; + pBaseColors[1] = g1; + pBaseColors[2] = b1; + pBaseColors[3] = r2; + pBaseColors[4] = g2; + pBaseColors[5] = b2; +} + +static etc1_uint32 chooseModifier(const etc1_byte* pBaseColors, + const etc1_byte* pIn, etc1_uint32 *pLow, int bitIndex, + const int* pModifierTable) { + etc1_uint32 bestScore = ~0; + int bestIndex = 0; + int pixelR = pIn[0]; + int pixelG = pIn[1]; + int pixelB = pIn[2]; + int r = pBaseColors[0]; + int g = pBaseColors[1]; + int b = pBaseColors[2]; + for (int i = 0; i < 4; i++) { + int modifier = pModifierTable[i]; + int decodedG = etc1_clamp(g + modifier); + etc1_uint32 score = (etc1_uint32) (6 * square(decodedG - pixelG)); + if (score >= bestScore) { + continue; + } + int decodedR = etc1_clamp(r + modifier); + score += (etc1_uint32) (3 * square(decodedR - pixelR)); + if (score >= bestScore) { + continue; + } + int decodedB = etc1_clamp(b + modifier); + score += (etc1_uint32) square(decodedB - pixelB); + if (score < bestScore) { + bestScore = score; + bestIndex = i; + } + } + etc1_uint32 lowMask = (((bestIndex >> 1) << 16) | (bestIndex & 1)) + << bitIndex; + *pLow |= lowMask; + return bestScore; +} + +static +void etc_encode_subblock_helper(const etc1_byte* pIn, etc1_uint32 inMask, etc_compressed* pCompressed, bool flipped, bool second, const etc1_byte* pBaseColors, const int* pModifierTable) { + int score = pCompressed->score; + if (flipped) { + int by = 0; + if (second) { + by = 2; + } + for (int y = 0; y < 2; y++) { + int yy = by + y; + for (int x = 0; x < 4; x++) { + int i = x + 4 * yy; + if (inMask & (1 << i)) { + score += chooseModifier(pBaseColors, pIn + i * 3, + &pCompressed->low, yy + x * 4, pModifierTable); + } + } + } + } else { + int bx = 0; + if (second) { + bx = 2; + } + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 2; x++) { + int xx = bx + x; + int i = xx + 4 * y; + if (inMask & (1 << i)) { + score += chooseModifier(pBaseColors, pIn + i * 3, + &pCompressed->low, y + xx * 4, pModifierTable); + } + } + } + } + pCompressed->score = score; +} + +static +void etc_encode_block_helper(const etc1_byte* pIn, etc1_uint32 inMask, const etc1_byte* pColors, etc_compressed* pCompressed, bool flipped) { + pCompressed->score = ~0; + pCompressed->high = (flipped ? 1 : 0); + pCompressed->low = 0; + + etc1_byte pBaseColors[6]; + + etc_encodeBaseColors(pBaseColors, pColors, pCompressed); + + int originalHigh = pCompressed->high; + + const int* pModifierTable = kModifierTable; + for (int i = 0; i < 8; i++, pModifierTable += 4) { + etc_compressed temp; + temp.score = 0; + temp.high = originalHigh | (i << 5); + temp.low = 0; + etc_encode_subblock_helper(pIn, inMask, &temp, flipped, false, + pBaseColors, pModifierTable); + take_best(pCompressed, &temp); + } + pModifierTable = kModifierTable; + etc_compressed firstHalf = *pCompressed; + for (int i = 0; i < 8; i++, pModifierTable += 4) { + etc_compressed temp; + temp.score = firstHalf.score; + temp.high = firstHalf.high | (i << 2); + temp.low = firstHalf.low; + etc_encode_subblock_helper(pIn, inMask, &temp, flipped, true, + pBaseColors + 3, pModifierTable); + if (i == 0) { + *pCompressed = temp; + } else { + take_best(pCompressed, &temp); + } + } +} + +// 4 x 4 x 3 x 8 bit + 16 bit in -> 8 * 8 bit out +// Input is a 4 x 4 square of 3-byte pixels in form R, G, B +// inmask is a 16-bit mask where bit (1 << (x + y * 4)) tells whether the corresponding (x,y) +// pixel is valid or not. Invalid pixel color values are ignored when compressing. +// Output is an ETC1 compressed version of the data. +static +void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 inMask, etc1_byte* pOut) { + etc1_byte colors[6]; + etc1_byte flippedColors[6]; + etc_average_colors_subblock(pIn, inMask, colors, false, false); + etc_average_colors_subblock(pIn, inMask, colors + 3, false, true); + etc_average_colors_subblock(pIn, inMask, flippedColors, true, false); + etc_average_colors_subblock(pIn, inMask, flippedColors + 3, true, true); + + etc_compressed a, b; + etc_encode_block_helper(pIn, inMask, colors, &a, false); + etc_encode_block_helper(pIn, inMask, flippedColors, &b, true); + take_best(&a, &b); + + //rsDebug("a.high",a.high); + //rsDebug("a.low",a.low); + //rsDebug("a.score",a.score); + + writeBigEndian(pOut, a.high); + writeBigEndian(pOut + 4, a.low); +} + +uchar * pInA; // uchar3 +uint32_t height; +uint32_t width; + +static etc1_uint32 pullBlockAndMask_from_565Raster(uint32_t bn, const etc1_byte* pIn, uint32_t height, uint32_t width, etc1_byte* block) { + static const unsigned short kYMask[] = { 0x0, 0xf, 0xff, 0xfff, 0xffff }; + static const unsigned short kXMask[] = { 0x0, 0x1111, 0x3333, 0x7777, + 0xffff }; + static const int pixelSize = 2; + + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + + //rsDebug("encodedWidth", encodedWidth); + //rsDebug("encodedHeight", encodedHeight); + + int by = bn / (encodedWidth / 4); + int bx = bn - (by * (encodedWidth / 4)); + + //rsDebug("bn", bn); + //rsDebug("by", by); + //rsDebug("bx", bx); + + int yEnd=4; + if(by == (encodedHeight/4)) { + yEnd = encodedHeight - height; + } + int ymask = kYMask[yEnd]; + + int xEnd=4; + if(bx == (encodedWidth/4)) { + xEnd = encodedWidth - width; + } + etc1_uint32 mask = ymask & kXMask[xEnd]; + + int stride = pixelSize * width; + + int x = bx * 4; + int y = by * 4; + + for (int cy = 0; cy < yEnd; cy++) { + etc1_byte* q = block + (cy * 4) * 3; + const etc1_byte* p = pIn + pixelSize * x + stride * (y + cy); + for (int cx = 0; cx < xEnd; cx++) { + int pixel = (p[1] << 8) | p[0]; + //rsDebug("pixel", pixel); + //rsDebug("pixelR", convert5To8(pixel >> 11)); + //rsDebug("pixelG", convert6To8(pixel >> 5)); + //rsDebug("pixelB", convert5To8(pixel)); + *q++ = convert5To8(pixel >> 11); + *q++ = convert6To8(pixel >> 5); + *q++ = convert5To8(pixel); + p += pixelSize; + } + } + return mask; +} + +static etc1_uint32 pullBlockAndMask_from_DXT3(uint32_t bn, const etc1_byte* pIn, uint32_t height, uint32_t width, etc1_byte* block) { + static const int pixelSize = 1; + int stride = pixelSize * width; + //ff_decode_dxt3(pIn,block,width,height,stride); + + return 0xffff; +} + +// processing of one ETC1 block +ushort4 __attribute__((kernel)) root(uint32_t x) { + //rsDebug("===========root==================",x); + + etc1_byte pOut [8]; + etc1_byte block [48]; + + // R, G, B. Byte (3 * (x + 4 * y) is the R value of pixel (x, y) + + //rsDebug("pInA", pInA); + etc1_uint32 amask = pullBlockAndMask_from_565Raster(x, pInA, height, width, block); + //rsDebug("mask",amask); + //for (int i = 0; i < 48; i++) { + // rsDebug("pixel",block[i]); + //} + + //rsDebug("etc1_encode_block call",0); + etc1_encode_block (block, amask, pOut); + + //rsDebug("pOut[0]",pOut[0]); + //rsDebug("pOut[1]",pOut[1]); + //rsDebug("pOut[2]",pOut[2]); + //rsDebug("pOut[3]",pOut[3]); + //rsDebug("pOut[4]",pOut[4]); + //rsDebug("pOut[5]",pOut[5]); + //rsDebug("pOut[6]",pOut[6]); + //rsDebug("pOut[7]",pOut[7]); + + ushort4 out; + out.x = pOut[0] | pOut[1] << 8; + out.y = pOut[2] | pOut[3] << 8; + out.z = pOut[4] | pOut[5] << 8; + out.w = pOut[6] | pOut[7] << 8; + + //rsDebug("out",out); + + return out; +} diff --git a/WorldWindowApplicationSample/project.properties b/WorldWindowApplicationSample/project.properties index 13c87d0..4e1f56f 100644 --- a/WorldWindowApplicationSample/project.properties +++ b/WorldWindowApplicationSample/project.properties @@ -13,3 +13,8 @@ # Project target. target=android-19 android.library.reference.1=../WorldWindAndroid +android.library=false +renderscript.target=19 +renderscript.support.mode=true +sdk.buildtools=19.0.3 + diff --git a/WorldWindowApplicationSample/src/config/Earth/GeoserverEtc1Test.xml b/WorldWindowApplicationSample/src/config/Earth/GeoserverEtc1Test.xml new file mode 100644 index 0000000..4c5e546 --- /dev/null +++ b/WorldWindowApplicationSample/src/config/Earth/GeoserverEtc1Test.xml @@ -0,0 +1,51 @@ + + + + + + + Trail + + http://192.168.1.2:8080/geoserver/wms + http://192.168.1.2:8080/geoserver/wms + topp:states + + + topp_states + image/dds; format=ETC1 + + image/dds; format=ETC1 + + .dds + + + + + + + + + + + + + + + + + + + + + + + + 2 + 1000 + diff --git a/WorldWindowApplicationSample/src/config/worldwind.layers2.xml b/WorldWindowApplicationSample/src/config/worldwind.layers2.xml index debeab6..6c10de6 100644 --- a/WorldWindowApplicationSample/src/config/worldwind.layers2.xml +++ b/WorldWindowApplicationSample/src/config/worldwind.layers2.xml @@ -19,8 +19,12 @@ href="config/Earth/BMNGWMSLayer.xml" /> --> - + href="config/Earth/LandsatI3WMSLayer.xml" /> + + >5) & 0x070007; + rb1 += (rb1>>5) & 0x070007; + g0 = (c0 <<5) & 0x00fc00; + g1 = (c1 <<5) & 0x00fc00; + g0 += (g0 >>6) & 0x000300; + g1 += (g1 >>6) & 0x000300; + + colors[0] = rb0 + g0 + a; + colors[1] = rb1 + g1 + a; + + if (c0 > c1 || flag) { + rb2 = (((2*rb0+rb1) * 21) >> 6) & 0xff00ff; + rb3 = (((2*rb1+rb0) * 21) >> 6) & 0xff00ff; + g2 = (((2*g0 +g1 ) * 21) >> 6) & 0x00ff00; + g3 = (((2*g1 +g0 ) * 21) >> 6) & 0x00ff00; + colors[3] = rb3 + g3 + a; + } else { + rb2 = ((rb0+rb1) >> 1) & 0xff00ff; + g2 = ((g0 +g1 ) >> 1) & 0x00ff00; + colors[3] = 0; + } + + colors[2] = rb2 + g2 + a; + + pixels = AV_RL32(s+4); + for (y=0; y<4; y++) { + for (x=0; x<4; x++) { + a = (alpha & 0x0f) << 28; + a += a >> 4; + d[x] = a + colors[pixels&3]; + pixels >>= 2; + alpha >>= 4; + } + d += qstride; + } + } + +typedef unsigned char etc1_byte; +typedef int etc1_bool; +typedef unsigned int etc1_uint32; + +typedef struct EtcCompressed { + etc1_uint32 high; + etc1_uint32 low; + etc1_uint32 score; // Lower is more accurate +} etc_compressed; + +/* From http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt + + The number of bits that represent a 4x4 texel block is 64 bits if + is given by ETC1_RGB8_OES. + + The data for a block is a number of bytes, + + {q0, q1, q2, q3, q4, q5, q6, q7} + + where byte q0 is located at the lowest memory address and q7 at + the highest. The 64 bits specifying the block is then represented + by the following 64 bit integer: + + int64bit = 256*(256*(256*(256*(256*(256*(256*q0+q1)+q2)+q3)+q4)+q5)+q6)+q7; + + ETC1_RGB8_OES: + + a) bit layout in bits 63 through 32 if diffbit = 0 + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 + ----------------------------------------------- + | base col1 | base col2 | base col1 | base col2 | + | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| + ----------------------------------------------- + + 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + --------------------------------------------------- + | base col1 | base col2 | table | table |diff|flip| + | B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + --------------------------------------------------- + + + b) bit layout in bits 63 through 32 if diffbit = 1 + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 + ----------------------------------------------- + | base col1 | dcol 2 | base col1 | dcol 2 | + | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | + ----------------------------------------------- + + 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + --------------------------------------------------- + | base col 1 | dcol 2 | table | table |diff|flip| + | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + --------------------------------------------------- + + + c) bit layout in bits 31 through 0 (in both cases) + + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 + ----------------------------------------------- + | most significant pixel index bits | + | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| + ----------------------------------------------- + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + -------------------------------------------------- + | least significant pixel index bits | + | p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + -------------------------------------------------- + + + Add table 3.17.2: Intensity modifier sets for ETC1 compressed textures: + + table codeword modifier table + ------------------ ---------------------- + 0 -8 -2 2 8 + 1 -17 -5 5 17 + 2 -29 -9 9 29 + 3 -42 -13 13 42 + 4 -60 -18 18 60 + 5 -80 -24 24 80 + 6 -106 -33 33 106 + 7 -183 -47 47 183 + + + Add table 3.17.3 Mapping from pixel index values to modifier values for + ETC1 compressed textures: + + pixel index value + --------------- + msb lsb resulting modifier value + ----- ----- ------------------------- + 1 1 -b (large negative value) + 1 0 -a (small negative value) + 0 0 a (small positive value) + 0 1 b (large positive value) + + + */ + +static const int kModifierTable[] = { +/* 0 */2, 8, -2, -8, +/* 1 */5, 17, -5, -17, +/* 2 */9, 29, -9, -29, +/* 3 */13, 42, -13, -42, +/* 4 */18, 60, -18, -60, +/* 5 */24, 80, -24, -80, +/* 6 */33, 106, -33, -106, +/* 7 */47, 183, -47, -183 }; + +static const int kLookup[8] = { 0, 1, 2, 3, -4, -3, -2, -1 }; + +static inline etc1_byte etc1_clamp(int x) { + return (etc1_byte) (x >= 0 ? (x < 255 ? x : 255) : 0); +} + +static +inline int convert4To8(int b) { + int c = b & 0xf; + return (c << 4) | c; +} + +static +inline int convert5To8(int b) { + int c = b & 0x1f; + return (c << 3) | (c >> 2); +} + +static +inline int convert6To8(int b) { + int c = b & 0x3f; + return (c << 2) | (c >> 4); +} + +static +inline int divideBy255(int d) { + return (d + 128 + (d >> 8)) >> 8; +} + +static +inline int convert8To4(int b) { + int c = b & 0xff; + return divideBy255(c * 15); +} + +static +inline int convert8To5(int b) { + int c = b & 0xff; + return divideBy255(c * 31); +} + +static +inline int convertDiff(int base, int diff) { + return convert5To8((0x1f & base) + kLookup[0x7 & diff]); +} + +static +inline void take_best(etc_compressed* a, const etc_compressed* b) { + if (a->score > b->score) { + *a = *b; + } +} + +static void writeBigEndian(etc1_byte* pOut, etc1_uint32 d) { + pOut[0] = (etc1_byte)(d >> 24); + pOut[1] = (etc1_byte)(d >> 16); + pOut[2] = (etc1_byte)(d >> 8); + pOut[3] = (etc1_byte) d; +} + +static bool inRange4bitSigned(int color) { + return color >= -4 && color <= 3; +} + +static +inline int square(int x) { + return x * x; +} + +static +void etc_average_colors_subblock(const etc1_byte* pIn, etc1_uint32 inMask, etc1_byte* pColors, bool flipped, bool second) { + int r = 0; + int g = 0; + int b = 0; + + if (flipped) { + int by = 0; + if (second) { + by = 2; + } + for (int y = 0; y < 2; y++) { + int yy = by + y; + for (int x = 0; x < 4; x++) { + int i = x + 4 * yy; + if (inMask & (1 << i)) { + const etc1_byte* p = pIn + i * 3; + r += *(p++); + g += *(p++); + b += *(p++); + } + } + } + } else { + int bx = 0; + if (second) { + bx = 2; + } + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 2; x++) { + int xx = bx + x; + int i = xx + 4 * y; + if (inMask & (1 << i)) { + const etc1_byte* p = pIn + i * 3; + r += *(p++); + g += *(p++); + b += *(p++); + } + } + } + } + pColors[0] = (etc1_byte)((r + 4) >> 3); + pColors[1] = (etc1_byte)((g + 4) >> 3); + pColors[2] = (etc1_byte)((b + 4) >> 3); +} + +static +void etc_encodeBaseColors(etc1_byte* pBaseColors, const etc1_byte* pColors, etc_compressed* pCompressed) { + int r1, g1, b1, r2, g2, b2; // 8 bit base colors for sub-blocks + bool differential; + { + int r51 = convert8To5(pColors[0]); + int g51 = convert8To5(pColors[1]); + int b51 = convert8To5(pColors[2]); + int r52 = convert8To5(pColors[3]); + int g52 = convert8To5(pColors[4]); + int b52 = convert8To5(pColors[5]); + + r1 = convert5To8(r51); + g1 = convert5To8(g51); + b1 = convert5To8(b51); + + int dr = r52 - r51; + int dg = g52 - g51; + int db = b52 - b51; + + differential = inRange4bitSigned(dr) && inRange4bitSigned(dg) + && inRange4bitSigned(db); + if (differential) { + r2 = convert5To8(r51 + dr); + g2 = convert5To8(g51 + dg); + b2 = convert5To8(b51 + db); + pCompressed->high |= (r51 << 27) | ((7 & dr) << 24) | (g51 << 19) + | ((7 & dg) << 16) | (b51 << 11) | ((7 & db) << 8) | 2; + } + } + + if (!differential) { + int r41 = convert8To4(pColors[0]); + int g41 = convert8To4(pColors[1]); + int b41 = convert8To4(pColors[2]); + int r42 = convert8To4(pColors[3]); + int g42 = convert8To4(pColors[4]); + int b42 = convert8To4(pColors[5]); + r1 = convert4To8(r41); + g1 = convert4To8(g41); + b1 = convert4To8(b41); + r2 = convert4To8(r42); + g2 = convert4To8(g42); + b2 = convert4To8(b42); + pCompressed->high |= (r41 << 28) | (r42 << 24) | (g41 << 20) | (g42 + << 16) | (b41 << 12) | (b42 << 8); + } + pBaseColors[0] = r1; + pBaseColors[1] = g1; + pBaseColors[2] = b1; + pBaseColors[3] = r2; + pBaseColors[4] = g2; + pBaseColors[5] = b2; +} + +static etc1_uint32 chooseModifier(const etc1_byte* pBaseColors, + const etc1_byte* pIn, etc1_uint32 *pLow, int bitIndex, + const int* pModifierTable) { + etc1_uint32 bestScore = ~0; + int bestIndex = 0; + int pixelR = pIn[0]; + int pixelG = pIn[1]; + int pixelB = pIn[2]; + int r = pBaseColors[0]; + int g = pBaseColors[1]; + int b = pBaseColors[2]; + for (int i = 0; i < 4; i++) { + int modifier = pModifierTable[i]; + int decodedG = etc1_clamp(g + modifier); + etc1_uint32 score = (etc1_uint32) (6 * square(decodedG - pixelG)); + if (score >= bestScore) { + continue; + } + int decodedR = etc1_clamp(r + modifier); + score += (etc1_uint32) (3 * square(decodedR - pixelR)); + if (score >= bestScore) { + continue; + } + int decodedB = etc1_clamp(b + modifier); + score += (etc1_uint32) square(decodedB - pixelB); + if (score < bestScore) { + bestScore = score; + bestIndex = i; + } + } + etc1_uint32 lowMask = (((bestIndex >> 1) << 16) | (bestIndex & 1)) + << bitIndex; + *pLow |= lowMask; + return bestScore; +} + +static +void etc_encode_subblock_helper(const etc1_byte* pIn, etc1_uint32 inMask, etc_compressed* pCompressed, bool flipped, bool second, const etc1_byte* pBaseColors, const int* pModifierTable) { + int score = pCompressed->score; + if (flipped) { + int by = 0; + if (second) { + by = 2; + } + for (int y = 0; y < 2; y++) { + int yy = by + y; + for (int x = 0; x < 4; x++) { + int i = x + 4 * yy; + if (inMask & (1 << i)) { + score += chooseModifier(pBaseColors, pIn + i * 3, + &pCompressed->low, yy + x * 4, pModifierTable); + } + } + } + } else { + int bx = 0; + if (second) { + bx = 2; + } + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 2; x++) { + int xx = bx + x; + int i = xx + 4 * y; + if (inMask & (1 << i)) { + score += chooseModifier(pBaseColors, pIn + i * 3, + &pCompressed->low, y + xx * 4, pModifierTable); + } + } + } + } + pCompressed->score = score; +} + +static +void etc_encode_block_helper(const etc1_byte* pIn, etc1_uint32 inMask, const etc1_byte* pColors, etc_compressed* pCompressed, bool flipped) { + pCompressed->score = ~0; + pCompressed->high = (flipped ? 1 : 0); + pCompressed->low = 0; + + etc1_byte pBaseColors[6]; + + etc_encodeBaseColors(pBaseColors, pColors, pCompressed); + + int originalHigh = pCompressed->high; + + const int* pModifierTable = kModifierTable; + for (int i = 0; i < 8; i++, pModifierTable += 4) { + etc_compressed temp; + temp.score = 0; + temp.high = originalHigh | (i << 5); + temp.low = 0; + etc_encode_subblock_helper(pIn, inMask, &temp, flipped, false, + pBaseColors, pModifierTable); + take_best(pCompressed, &temp); + } + pModifierTable = kModifierTable; + etc_compressed firstHalf = *pCompressed; + for (int i = 0; i < 8; i++, pModifierTable += 4) { + etc_compressed temp; + temp.score = firstHalf.score; + temp.high = firstHalf.high | (i << 2); + temp.low = firstHalf.low; + etc_encode_subblock_helper(pIn, inMask, &temp, flipped, true, + pBaseColors + 3, pModifierTable); + if (i == 0) { + *pCompressed = temp; + } else { + take_best(pCompressed, &temp); + } + } +} + +// 4 x 4 x 3 x 8 bit + 16 bit in -> 8 * 8 bit out +// Input is a 4 x 4 square of 3-byte pixels in form R, G, B +// inmask is a 16-bit mask where bit (1 << (x + y * 4)) tells whether the corresponding (x,y) +// pixel is valid or not. Invalid pixel color values are ignored when compressing. +// Output is an ETC1 compressed version of the data. +static +void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 inMask, etc1_byte* pOut) { + etc1_byte colors[6]; + etc1_byte flippedColors[6]; + etc_average_colors_subblock(pIn, inMask, colors, false, false); + etc_average_colors_subblock(pIn, inMask, colors + 3, false, true); + etc_average_colors_subblock(pIn, inMask, flippedColors, true, false); + etc_average_colors_subblock(pIn, inMask, flippedColors + 3, true, true); + + etc_compressed a, b; + etc_encode_block_helper(pIn, inMask, colors, &a, false); + etc_encode_block_helper(pIn, inMask, flippedColors, &b, true); + take_best(&a, &b); + + //rsDebug("a.high",a.high); + //rsDebug("a.low",a.low); + //rsDebug("a.score",a.score); + + writeBigEndian(pOut, a.high); + writeBigEndian(pOut + 4, a.low); +} + +uchar * pInA; // uchar3 +uint32_t height; +uint32_t width; + +static etc1_uint32 pullBlockAndMask_from_565Raster(uint32_t bn, const etc1_byte* pIn, uint32_t height, uint32_t width, etc1_byte* block) { + static const unsigned short kYMask[] = { 0x0, 0xf, 0xff, 0xfff, 0xffff }; + static const unsigned short kXMask[] = { 0x0, 0x1111, 0x3333, 0x7777, + 0xffff }; + static const int pixelSize = 2; + + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + + //rsDebug("encodedWidth", encodedWidth); + //rsDebug("encodedHeight", encodedHeight); + + int by = bn / (encodedWidth / 4); + int bx = bn - (by * (encodedWidth / 4)); + + //rsDebug("bn", bn); + //rsDebug("by", by); + //rsDebug("bx", bx); + + int yEnd=4; + if(by == (encodedHeight/4)) { + yEnd = encodedHeight - height; + } + int ymask = kYMask[yEnd]; + + int xEnd=4; + if(bx == (encodedWidth/4)) { + xEnd = encodedWidth - width; + } + etc1_uint32 mask = ymask & kXMask[xEnd]; + + int stride = pixelSize * width; + + int x = bx * 4; + int y = by * 4; + + for (int cy = 0; cy < yEnd; cy++) { + etc1_byte* q = block + (cy * 4) * 3; + const etc1_byte* p = pIn + pixelSize * x + stride * (y + cy); + for (int cx = 0; cx < xEnd; cx++) { + int pixel = (p[1] << 8) | p[0]; + //rsDebug("pixel", pixel); + //rsDebug("pixelR", convert5To8(pixel >> 11)); + //rsDebug("pixelG", convert6To8(pixel >> 5)); + //rsDebug("pixelB", convert5To8(pixel)); + *q++ = convert5To8(pixel >> 11); + *q++ = convert6To8(pixel >> 5); + *q++ = convert5To8(pixel); + p += pixelSize; + } + } + return mask; +} + +static etc1_uint32 pullBlockAndMask_from_DXT3(uint32_t bn, const etc1_byte* pIn, uint32_t height, uint32_t width, etc1_byte* block) { + static const int pixelSize = 1; + int stride = pixelSize * width; + //ff_decode_dxt3(pIn,block,width,height,stride); + + return 0xffff; +} + +// processing of one ETC1 block +ushort4 __attribute__((kernel)) root(uint32_t x) { + //rsDebug("===========root==================",x); + + etc1_byte pOut [8]; + etc1_byte block [48]; + + // R, G, B. Byte (3 * (x + 4 * y) is the R value of pixel (x, y) + + //rsDebug("pInA", pInA); + etc1_uint32 amask = pullBlockAndMask_from_565Raster(x, pInA, height, width, block); + //rsDebug("mask",amask); + //for (int i = 0; i < 48; i++) { + // rsDebug("pixel",block[i]); + //} + + //rsDebug("etc1_encode_block call",0); + etc1_encode_block (block, amask, pOut); + + //rsDebug("pOut[0]",pOut[0]); + //rsDebug("pOut[1]",pOut[1]); + //rsDebug("pOut[2]",pOut[2]); + //rsDebug("pOut[3]",pOut[3]); + //rsDebug("pOut[4]",pOut[4]); + //rsDebug("pOut[5]",pOut[5]); + //rsDebug("pOut[6]",pOut[6]); + //rsDebug("pOut[7]",pOut[7]); + + ushort4 out; + out.x = pOut[0] | pOut[1] << 8; + out.y = pOut[2] | pOut[3] << 8; + out.z = pOut[4] | pOut[5] << 8; + out.w = pOut[6] | pOut[7] << 8; + + //rsDebug("out",out); + + return out; +} From 0803f02e464ee72fc175c66e1997487a89b75610 Mon Sep 17 00:00:00 2001 From: nicastel Date: Sat, 17 May 2014 02:13:10 +0200 Subject: [PATCH 18/23] deactivation of the client side renderscript texture compression by default => still experimental, generated cache are ok but cause crash Add of a WMS ETC1 example layer --- WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml | 4 +++- WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml b/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml index f232020..c42aa05 100644 --- a/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml +++ b/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml @@ -23,7 +23,9 @@ image/png image/dds - .dds + .jpg + diff --git a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml index 394a69e..3fdd8a4 100644 --- a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml +++ b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml @@ -16,7 +16,9 @@ image/png image/dds - .dds + .jpg + From 59176cc96314001e4977bb86556e286df4b4867b Mon Sep 17 00:00:00 2001 From: nicastel Date: Sun, 18 May 2014 08:11:06 +0200 Subject: [PATCH 19/23] fix memory leak and add example layer from my openshift geoserver --- .../renderscripttexturecompressor/etc1/rs/RsETC1.java | 7 +++++-- .../src/config/Earth/GeoserverEtc1Test.xml | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java index 2efc4c8..1c8f259 100644 --- a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java @@ -3,7 +3,6 @@ import java.nio.ByteBuffer; import nicastel.renderscripttexturecompressor.etc1.rs.ScriptC_etc1compressor; - import android.support.v8.renderscript.RenderScript; import android.support.v8.renderscript.Allocation; import android.support.v8.renderscript.Element; @@ -109,7 +108,7 @@ public static int encodeImage(RenderScript rs, ScriptC_etc1compressor script, By // int iOut = 0; - int size = width * height / (DECODED_BLOCK_SIZE / 3); + int size = Math.max(width * height / (DECODED_BLOCK_SIZE / 3), 1); Allocation p00 = Allocation.createSized(rs, Element.U8(rs), width * height * pixelSize); Allocation aout = Allocation.createSized(rs, Element.U16_4(rs), size); @@ -131,8 +130,12 @@ public static int encodeImage(RenderScript rs, ScriptC_etc1compressor script, By System.out.println("tExec : "+tExec+" ms"); long tFillOut = java.lang.System.currentTimeMillis(); + + p00.destroy(); + short[] arrayOut3Temp = new short[4*size]; aout.copyTo(arrayOut3Temp); + aout.destroy(); Allocation aout2 = Allocation.createSized(rs, Element.U8(rs), 8*size); aout2.copyFromUnchecked(arrayOut3Temp); diff --git a/WorldWindowApplicationSample/src/config/Earth/GeoserverEtc1Test.xml b/WorldWindowApplicationSample/src/config/Earth/GeoserverEtc1Test.xml index 4c5e546..6e48142 100644 --- a/WorldWindowApplicationSample/src/config/Earth/GeoserverEtc1Test.xml +++ b/WorldWindowApplicationSample/src/config/Earth/GeoserverEtc1Test.xml @@ -8,10 +8,10 @@ - Trail + ETC1 layer - http://192.168.1.2:8080/geoserver/wms - http://192.168.1.2:8080/geoserver/wms + http://mygeoserver-nicastel.rhcloud.com/geoserver/wms + http://mygeoserver-nicastel.rhcloud.com/geoserver/wms topp:states diff --git a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml index 3fdd8a4..172729b 100644 --- a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml +++ b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml @@ -16,7 +16,7 @@ image/png image/dds - .jpg + .dds From 8f487f35c4eafc058a35ff80523603075fa23128 Mon Sep 17 00:00:00 2001 From: nicastel Date: Thu, 22 May 2014 23:56:01 +0200 Subject: [PATCH 21/23] fix handling of DDS DXT/ETC texture without mipmap (from mapserver DDS plugin for example) --- .../src/gov/nasa/worldwind/util/dds/DDSTextureReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSTextureReader.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSTextureReader.java index dcfb12b..1b87dee 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSTextureReader.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSTextureReader.java @@ -79,7 +79,7 @@ protected GpuTextureData doRead(InputStream stream) throws IOException throw new IllegalArgumentException(msg); } - int mipmapCount = header.getMipMapCount(); + int mipmapCount = Math.max(1,header.getMipMapCount()); long estimatedMemorySize = 0; ByteBuffer buffer = WWIO.readStreamToBuffer(stream); From 1d1f46d88746d8b559886e77732baef4e27a537a Mon Sep 17 00:00:00 2001 From: nicastel Date: Sat, 24 May 2014 03:06:33 +0200 Subject: [PATCH 22/23] Improvement and bugs fixes Renderscript Compressor : Update to last version of Renderscript Compressor Renderscript Compressor : Handling of RGBA 8888 Images (still ignoring alpha) Renderscript Compressor : Improve performance of DDS/ETC1 with mipmap compression Add of an example layer from my EC2 Mapserver with DDS/ETC1 plugin Switch to an external datastore --- WorldWindAndroid/project.properties | 2 +- .../src/config/Earth/LandsatI3WMSLayer.xml | 2 +- .../AbstractRetrievalPostProcessor.java | 23 +- .../worldwind/util/dds/DDSCompressor.java | 7 +- .../dds/ETC1Compressor.java | 48 ++- .../dds/ETC1DDSCompressor.java | 351 ++++++++++++++++++ .../dds/ETCConstants.java | 7 + .../etc1/rs/RsETC1.java | 160 ++------ .../etc1/rs/RsETC1Util.java | 59 ++- .../etc1/rs/etc1compressor.rs | 74 ++-- .../Earth/BlackMarbleMapserverEtc1Test.xml | 51 +++ .../src/config/worldwind.layers2.xml | 3 + .../android/ww/WorldWindowActivity.java | 4 +- .../src/renderscript/etc1compressor.rs | 74 ++-- 14 files changed, 613 insertions(+), 252 deletions(-) rename WorldWindAndroid/src/{gov/nasa/worldwind/util => nicastel/renderscripttexturecompressor}/dds/ETC1Compressor.java (64%) create mode 100644 WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java create mode 100644 WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETCConstants.java create mode 100644 WorldWindowApplicationSample/src/config/Earth/BlackMarbleMapserverEtc1Test.xml diff --git a/WorldWindAndroid/project.properties b/WorldWindAndroid/project.properties index bcbfcc6..513bc8e 100644 --- a/WorldWindAndroid/project.properties +++ b/WorldWindAndroid/project.properties @@ -13,6 +13,6 @@ # Project target. target=android-19 android.library=true -renderscript.target=19 +renderscript.target=18 renderscript.support.mode=true sdk.buildtools=19.0.3 diff --git a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml index 172729b..3fdd8a4 100644 --- a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml +++ b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml @@ -16,7 +16,7 @@ image/png image/dds - .dds + .jpg diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/retrieve/AbstractRetrievalPostProcessor.java b/WorldWindAndroid/src/gov/nasa/worldwind/retrieve/AbstractRetrievalPostProcessor.java index 3cb5abf..7a430b9 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/retrieve/AbstractRetrievalPostProcessor.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/retrieve/AbstractRetrievalPostProcessor.java @@ -6,16 +6,24 @@ package gov.nasa.worldwind.retrieve; -import android.graphics.Bitmap; -import gov.nasa.worldwind.avlist.*; -import gov.nasa.worldwind.util.*; -import gov.nasa.worldwind.util.dds.DDSCompressor; - -import java.io.*; +import gov.nasa.worldwind.avlist.AVKey; +import gov.nasa.worldwind.avlist.AVList; +import gov.nasa.worldwind.util.ImageUtil; +import gov.nasa.worldwind.util.Logging; +import gov.nasa.worldwind.util.WWIO; +import gov.nasa.worldwind.util.WWUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.net.HttpURLConnection; import java.nio.ByteBuffer; import java.nio.channels.ClosedByInterruptException; +import nicastel.renderscripttexturecompressor.dds.ETC1DDSCompressor; +import android.graphics.Bitmap; + /** * Abstract base class for retrieval post-processors. Verifies the retrieval operation and dispatches the content to the * a subclasses content handlers. @@ -226,6 +234,7 @@ protected boolean saveBuffer() throws IOException protected boolean saveBuffer(ByteBuffer buffer) throws IOException { File outFile = this.getOutputFile(); + System.out.println("outFile : "+outFile); if (outFile == null) return false; @@ -641,7 +650,7 @@ protected ByteBuffer convertToDDS() throws IOException // if (image != null) // buffer = DDSCompressor.compressImage(image); // else - buffer = DDSCompressor.compressImageBuffer(this.getRetriever().getBuffer()); + buffer = ETC1DDSCompressor.compressImageBuffer(this.getRetriever().getBuffer()); return buffer; } diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSCompressor.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSCompressor.java index 3477dcc..f5dd18b 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSCompressor.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSCompressor.java @@ -10,9 +10,10 @@ import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.WWIO; import gov.nasa.worldwind.util.WWMath; +import nicastel.renderscripttexturecompressor.dds.ETC1Compressor; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; /** @@ -63,7 +64,7 @@ public static java.nio.ByteBuffer compressImageStream(java.io.InputStream inputS Options opts = new BitmapFactory.Options(); //opts.inPreferQualityOverSpeed = false; - opts.inPreferredConfig = Config.RGB_565; + opts.inPreferredConfig = Config.ARGB_8888; Bitmap image = BitmapFactory.decodeStream(inputStream, null, opts); if (image == null) { @@ -385,7 +386,7 @@ protected Bitmap[] buildMipMaps(Bitmap image, DXTCompressionAttributes attribute // data is accessed directly. In this case, such code would be responsible for recognizing the color model // (premultiplied) and behaving accordingly. - Bitmap.Config mipmapImageType = Bitmap.Config.RGB_565; + Bitmap.Config mipmapImageType = Bitmap.Config.ARGB_8888; int maxLevel = ImageUtil.getMaxMipmapLevel(image.getWidth(), image.getHeight()); return ImageUtil.buildMipmaps(image, mipmapImageType, maxLevel); diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/ETC1Compressor.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1Compressor.java similarity index 64% rename from WorldWindAndroid/src/gov/nasa/worldwind/util/dds/ETC1Compressor.java rename to WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1Compressor.java index f261e60..6cf9714 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/ETC1Compressor.java +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1Compressor.java @@ -4,9 +4,13 @@ * All Rights Reserved. */ -package gov.nasa.worldwind.util.dds; +package nicastel.renderscripttexturecompressor.dds; import gov.nasa.worldwind.util.Logging; +import gov.nasa.worldwind.util.dds.BasicColorBlockExtractor; +import gov.nasa.worldwind.util.dds.ColorBlockExtractor; +import gov.nasa.worldwind.util.dds.DXTCompressionAttributes; +import gov.nasa.worldwind.util.dds.DXTCompressor; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -14,6 +18,8 @@ import nicastel.renderscripttexturecompressor.etc1.rs.RsETC1; import nicastel.renderscripttexturecompressor.etc1.rs.ScriptC_etc1compressor; import android.graphics.Bitmap; +import android.support.v8.renderscript.Allocation; +import android.support.v8.renderscript.Allocation.MipmapControl; import android.support.v8.renderscript.RenderScript; /** @@ -29,7 +35,7 @@ public ETC1Compressor() public int getDXTFormat() { - return DDSConstants.D3DFMT_ETC1; + return ETCConstants.D3DFMT_ETC1; } public int getCompressedSize(Bitmap image, DXTCompressionAttributes attributes) @@ -81,24 +87,34 @@ public void compressImage(Bitmap image, DXTCompressionAttributes attributes, } // TODO - int width = Math.max(image.getWidth(), 4); - int height = Math.max(image.getHeight(), 4); +// ByteBuffer bufferIn = ByteBuffer.allocateDirect( +// image.getRowBytes() * image.getHeight()).order( +// ByteOrder.nativeOrder()); +// image.copyPixelsToBuffer(bufferIn); +// bufferIn.rewind(); - int encodedImageSize = RsETC1.getEncodedDataSize(width, height); - System.out.println("encodedImageSize : "+encodedImageSize); - - // TODO - ByteBuffer bufferIn = ByteBuffer.allocateDirect( - image.getRowBytes() * image.getHeight()).order( - ByteOrder.nativeOrder()); - image.copyPixelsToBuffer(bufferIn); - bufferIn.rewind(); + MipmapControl control = MipmapControl.MIPMAP_NONE; + int usage = Allocation.USAGE_SHARED; + if(attributes.isBuildMipmaps()) { + // Needs an ARGB 8888 Bitmap as input + control = MipmapControl.MIPMAP_FULL; + usage = Allocation.USAGE_SCRIPT; + + } - ByteBuffer bufferOut = ByteBuffer.allocateDirect(encodedImageSize); + Allocation alloc = Allocation.createFromBitmap(rs, image, control, usage); + alloc.generateMipmaps(); - + int pixelSize = image.getRowBytes()/image.getWidth(); + + int encodedImageSize = Math.max(alloc.getBytesSize() / ((RsETC1.DECODED_BLOCK_SIZE/3)*pixelSize), 1)*8; + //System.out.println("encodedImageSize : "+encodedImageSize); + + ByteBuffer bufferOut = ByteBuffer.allocateDirect(encodedImageSize); + + RsETC1.encodeImage(rs, script, alloc, image.getWidth(), image.getHeight(), pixelSize, image.getRowBytes(), bufferOut, attributes.isBuildMipmaps()); - RsETC1.encodeImage(rs, script, bufferIn, image.getWidth(), image.getHeight(), image.getRowBytes()/image.getWidth(), image.getRowBytes(), bufferOut); + alloc.destroy(); bufferOut.rewind(); diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java new file mode 100644 index 0000000..6e81060 --- /dev/null +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java @@ -0,0 +1,351 @@ +package nicastel.renderscripttexturecompressor.dds; + +import gov.nasa.worldwind.util.ImageUtil; +import gov.nasa.worldwind.util.Logging; +import gov.nasa.worldwind.util.WWIO; +import gov.nasa.worldwind.util.WWMath; +import gov.nasa.worldwind.util.dds.DDSCompressor; +import gov.nasa.worldwind.util.dds.DDSConstants; +import gov.nasa.worldwind.util.dds.DDSHeader; +import gov.nasa.worldwind.util.dds.DDSPixelFormat; +import gov.nasa.worldwind.util.dds.DXT1Compressor; +import gov.nasa.worldwind.util.dds.DXT3Compressor; +import gov.nasa.worldwind.util.dds.DXTCompressionAttributes; +import gov.nasa.worldwind.util.dds.DXTCompressor; + +import java.nio.ByteBuffer; + +import nicastel.renderscripttexturecompressor.etc1.rs.RsETC1; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory.Options; + +public class ETC1DDSCompressor { + protected DXTCompressor getDXTCompressor(Bitmap image, DXTCompressionAttributes attributes) + { + // If the caller specified a DXT format in the attributes, then we return a compressor matching that format. + // Otherwise, we choose one automatically from the image type. If no choice can be made from the image type, + // we default to using a DXT3 compressor. + + if (attributes.getDXTFormat() == DDSConstants.D3DFMT_DXT1) + { + return new DXT1Compressor(); + } + else if (attributes.getDXTFormat() == ETCConstants.D3DFMT_ETC1) + { + return new ETC1Compressor(); + } + else if (attributes.getDXTFormat() == DDSConstants.D3DFMT_DXT2 + || attributes.getDXTFormat() == DDSConstants.D3DFMT_DXT3) + { + return new DXT3Compressor(); + } + else if (!image.hasAlpha()) + { + return new DXT1Compressor(); + } + else + { + return new DXT3Compressor(); + } + } + + protected Bitmap[] buildMipMaps(Bitmap image, DXTCompressionAttributes attributes) { + // Build the mipmap chain using a premultiplied alpha image format. This is necessary to ensure that + // transparent colors do not bleed into the opaque colors. For example, without premultiplied alpha the colors + // in a totally transparent pixel may contribute when one mipmap level is filtered (with either a box or a + // bilinear filter) to produce the pixels for the next level. + // + // The DXT color block extractor typically accessed Bitmap data via a call to getRGB(). This returns + // a packed 8888 ARGB int, where the color components are known to be not premultiplied, and in the sRGB color + // space. Therefore computing mipmaps in this way does not affect the rest of the DXT pipeline, unless color + // data is accessed directly. In this case, such code would be responsible for recognizing the color model + // (premultiplied) and behaving accordingly. + + Bitmap.Config mipmapImageType = Bitmap.Config.ARGB_8888; + int maxLevel = ImageUtil.getMaxMipmapLevel(image.getWidth(), image.getHeight()); + + if(attributes.getDXTFormat() == ETCConstants.D3DFMT_ETC1) { + // mipmaps are computed with renderscript API + Bitmap [] arr = {image}; + return arr; + } else { + return ImageUtil.buildMipmaps(image, mipmapImageType, maxLevel); + } + + } + + /** + * Convenience method to convert the specified imageBuffer to DDS according to the default attributes. + * The bytes in imageBuffer must be readable by {@link BitmapFactory#decodeStream + * BitmapFactory.decodeStream}. Once the image data is read, this is equivalent to calling {@link #compressImage(Bitmap)} with the Bitmap created by + * BitmapFactory. This returns null if the bytes + * inimageBuffer are not in a format understood by BitmapFactory. + * + * @param imageBuffer + * image file data to convert to the DDS file format. + * @return little endian ordered ByteBuffer containing the DDS file bytes, or null if the imageBuffer is not in a format understood by + * BitmapFactory. + * @throws IllegalArgumentException + * if imageBuffer is null. + */ + public static java.nio.ByteBuffer compressImageBuffer(java.nio.ByteBuffer imageBuffer) { + if (imageBuffer == null) { + String message = Logging.getMessage("nullValue.Image"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + + return compressImageBuffer(imageBuffer, DDSCompressor.getDefaultCompressionAttributes()); + } + + /** + * Convenience method to convert the specified imageBuffer to DDS according to the specified + * compression attributes. The bytes in imageBuffer must be readable by {@link BitmapFactory#decodeStream + * BitmapFactory.decodeStream}. Once the image data is read, this is equivalent to + * calling {@link #compressImage(Bitmap, DXTCompressionAttributes)} with the Bitmap created with the specified attributes. This returns null if + * the bytes in imageBuffer are not in a format + * understood by BitmapFactory. + * + * @param imageBuffer + * image file data to convert to the DDS file format. + * @param attributes + * attributes that control the compression. + * @return little endian ordered ByteBuffer containing the DDS file bytes, or null if the imageBuffer is not in a format understood by + * BitmapFactory. + * @throws IllegalArgumentException + * if either imageBuffer or attributes are null. + */ + public static java.nio.ByteBuffer compressImageBuffer(java.nio.ByteBuffer imageBuffer, DXTCompressionAttributes attributes) { + if (imageBuffer == null) { + String message = Logging.getMessage("nullValue.Image"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + + if (attributes == null) { + String message = Logging.getMessage("nullValue.AttributesIsNull"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + + java.io.InputStream inputStream = WWIO.getInputStreamFromByteBuffer(imageBuffer); + return compressImageStream(inputStream, attributes); + } + + /** + * Convenience method to convert the specified image stream to DDS according to the specified + * compression attributes. The stream must be readable by {@link BitmapFactory#decodeStream BitmapFactory.decodeStream}. Once the + * stream is read, this is equivalent + * to calling {@link #compressImage(Bitmap, DXTCompressionAttributes)} with the Bitmap created with the specified attributes. This returns null + * if the stream is not in a format understood by + * BitmapFactory. + * + * @param inputStream + * image stream to convert to the DDS file format. + * @param attributes + * attributes that control the compression. + * @return little endian ordered ByteBuffer containing the DDS file bytes, or null if the stream is not + * in a format understood by BitmapFactory. + * @throws IllegalArgumentException + * if either the stream or the attributes are null. + */ + public static java.nio.ByteBuffer compressImageStream(java.io.InputStream inputStream, DXTCompressionAttributes attributes) { + if (inputStream == null) { + String message = Logging.getMessage("nullValue.InputStreamIsNull"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + + if (attributes == null) { + String message = Logging.getMessage("nullValue.AttributesIsNull"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + + Options opts = new BitmapFactory.Options(); + //opts.inPreferQualityOverSpeed = false; + opts.inPreferredConfig = Config.ARGB_8888; + Bitmap image = BitmapFactory.decodeStream(inputStream, null, opts); + + if (image == null) { + return null; + } + + ETC1DDSCompressor compressor = new ETC1DDSCompressor(); + return compressor.compressImage(image, attributes); + } + + /** + * Converts the specified image to DDS according to the attributes. If the caller + * specified a DXT format in the attributes, then we return a compressor matching that format. Otherwise, we choose + * one automatically from the image type. If no choice can be made from the image type, we default to using a DXT3 + * compressor. + * + * @param image + * image to convert to the DDS file format. + * @param attributes + * attributes that control the compression. + * @return buffer little endian ordered ByteBuffer containing the dds file bytes. + * @throws IllegalArgumentException + * if either image or attributes are null, or if image has non power of two dimensions. + */ + public java.nio.ByteBuffer compressImage(Bitmap image, DXTCompressionAttributes attributes) { + if (image == null) { + String message = Logging.getMessage("nullValue.ImageIsNull"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + if (!WWMath.isPowerOfTwo(image.getWidth()) || !WWMath.isPowerOfTwo(image.getHeight())) { + String message = Logging.getMessage("generic.InvalidImageSize", image.getWidth(), image.getHeight()); + Logging.error(message); + throw new IllegalArgumentException(message); + } + if (attributes == null) { + String message = Logging.getMessage("nullValue.AttributesIsNull"); + Logging.error(message); + throw new IllegalArgumentException(message); + } + + DXTCompressor compressor = this.getDXTCompressor(image, attributes); + return this.doCompressImage(compressor, image, attributes); + } + + protected DDSHeader createDDSHeader(DXTCompressor compressor, Bitmap image, DXTCompressionAttributes attributes) { + DDSPixelFormat pixelFormat = new DDSPixelFormat(); + pixelFormat.setFlags(pixelFormat.getFlags() | DDSConstants.DDPF_FOURCC); + pixelFormat.setFourCC(compressor.getDXTFormat()); + + DDSHeader header = new DDSHeader(); + header.setFlags(header.getFlags() | DDSConstants.DDSD_WIDTH | DDSConstants.DDSD_HEIGHT | DDSConstants.DDSD_LINEARSIZE | DDSConstants.DDSD_PIXELFORMAT + | DDSConstants.DDSD_CAPS); + header.setWidth(image.getWidth()); + header.setHeight(image.getHeight()); + header.setLinearSize(compressor.getCompressedSize(image, attributes)); + header.setPixelFormat(pixelFormat); + header.setCaps(header.getCaps() | DDSConstants.DDSCAPS_TEXTURE); + + return header; + } + + /** + * Documentation on the DDS header format is available at http://msdn.microsoft.com/en-us/library/bb943982(VS.85).aspx. + * + * @param header + * header structure to write. + * @param buffer + * buffer that receives the header structure bytes. + */ + protected void writeDDSHeader(DDSHeader header, java.nio.ByteBuffer buffer) { + int pos = buffer.position(); + + buffer.putInt(header.getSize()); // dwSize + buffer.putInt(header.getFlags()); // dwFlags + buffer.putInt(header.getHeight()); // dwHeight + buffer.putInt(header.getWidth()); // dwWidth + buffer.putInt(header.getLinearSize()); // dwLinearSize + buffer.putInt(header.getDepth()); // dwDepth + buffer.putInt(header.getMipMapCount()); // dwMipMapCount + buffer.position(buffer.position() + 44); // dwReserved1[11] (unused) + this.writeDDSPixelFormat(header.getPixelFormat(), buffer); // ddpf + buffer.putInt(header.getCaps()); // dwCaps + buffer.putInt(header.getCaps2()); // dwCaps2 + buffer.putInt(header.getCaps3()); // dwCaps3 + buffer.putInt(header.getCaps4()); // dwCaps4 + buffer.position(buffer.position() + 4); // dwReserved2 (unused) + + buffer.position(pos + header.getSize()); + } + + /** + * Documentation on the DDS pixel format is available at http://msdn.microsoft.com/en-us/library/bb943984(VS.85).aspx. + * + * @param pixelFormat + * pixel format structure to write. + * @param buffer + * buffer that receives the pixel format structure bytes. + */ + protected void writeDDSPixelFormat(DDSPixelFormat pixelFormat, java.nio.ByteBuffer buffer) { + int pos = buffer.position(); + + buffer.putInt(pixelFormat.getSize()); // dwSize + buffer.putInt(pixelFormat.getFlags()); // dwFlags + buffer.putInt(pixelFormat.getFourCC()); // dwFourCC + buffer.putInt(pixelFormat.getRGBBitCount()); // dwRGBBitCount + buffer.putInt(pixelFormat.getRBitMask()); // dwRBitMask + buffer.putInt(pixelFormat.getGBitMask()); // dwGBitMask + buffer.putInt(pixelFormat.getBBitMask()); // dwBBitMask + buffer.putInt(pixelFormat.getABitMask()); // dwABitMask + + buffer.position(pos + pixelFormat.getSize()); + } + + protected java.nio.ByteBuffer createBuffer(int size) { + return java.nio.ByteBuffer.allocateDirect(size); + } + + protected ByteBuffer doCompressImage(DXTCompressor compressor, Bitmap image, DXTCompressionAttributes attributes) { + // Create the DDS header structure that describes the specified image, compressor, and compression attributes. + DDSHeader header = this.createDDSHeader(compressor, image, attributes); + + // Compute the DDS file size and mip map levels. If the attributes specify to build mip maps, then we compute + // the total file size including mip maps, create a chain of mip map images, and update the DDS header to + // describe the number of mip map levels. Otherwise, we compute the file size for a single image and do nothing + // to the DDS header. + Bitmap[] mipMapLevels = null; + int fileSize = 4 + header.getSize(); + + if (attributes.isBuildMipmaps()) { + mipMapLevels = this.buildMipMaps(image, attributes); + + int maxLevel = ImageUtil.getMaxMipmapLevel(image.getWidth(), image.getHeight()); + + if(attributes.getDXTFormat() == ETCConstants.D3DFMT_ETC1) { + // mipmaps are computed with renderscript API + int width = image.getWidth(); + int height = image.getHeight(); + for (int i = 1; i < maxLevel; i++) { + fileSize += RsETC1.getEncodedDataSize(width, height); + width /= 2; + height /= 2; + } + //System.out.println("fileSize : "+fileSize); + } else { + for (Bitmap mipMapImage : mipMapLevels) { + fileSize += compressor.getCompressedSize(mipMapImage, attributes); + } + } + + header.setFlags(header.getFlags() | DDSConstants.DDSD_MIPMAPCOUNT); + header.setMipMapCount(1+maxLevel); + } else { + fileSize += compressor.getCompressedSize(image, attributes); + } + + // Create a little endian buffer that holds the bytes of the DDS file. + java.nio.ByteBuffer buffer = this.createBuffer(fileSize); + buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN); + + // Write the DDS magic number and DDS header to the file. + buffer.putInt(DDSConstants.MAGIC); + this.writeDDSHeader(header, buffer); + + // Write the compressed DXT blocks to the DDS file. If the attributes specify to build mip maps, then we write + // each mip map level to the DDS file, starting with level 0 and ending with level N. Otherwise, we write a + // single image to the DDS file. + if (mipMapLevels == null) { + compressor.compressImage(image, attributes, buffer); + } else { + for (Bitmap mipMapImage : mipMapLevels) { + compressor.compressImage(mipMapImage, attributes, buffer); + } + } + + buffer.rewind(); + return buffer; + } + +} diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETCConstants.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETCConstants.java new file mode 100644 index 0000000..8b28fcb --- /dev/null +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETCConstants.java @@ -0,0 +1,7 @@ +package nicastel.renderscripttexturecompressor.dds; + +import gov.nasa.worldwind.util.dds.DDSConstants; + +public class ETCConstants { + public static final int D3DFMT_ETC1 = DDSConstants.makeFourCC('E', 'T', 'C', '1'); +} diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java index 1c8f259..de301a9 100644 --- a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java @@ -2,14 +2,15 @@ import java.nio.ByteBuffer; -import nicastel.renderscripttexturecompressor.etc1.rs.ScriptC_etc1compressor; -import android.support.v8.renderscript.RenderScript; +import android.graphics.Bitmap; import android.support.v8.renderscript.Allocation; +import android.support.v8.renderscript.Allocation.MipmapControl; import android.support.v8.renderscript.Element; +import android.support.v8.renderscript.RenderScript; public class RsETC1 { // Copyright 2009 Google Inc. - // 2011 Nicolas CASTEL + // Copyright 2011 Nicolas CASTEL // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,55 +30,15 @@ public class RsETC1 { public static final int ENCODED_BLOCK_SIZE = 8; /** - * Size in bytes of a decoded block. + * Size in pixel of a decoded block. */ public static final int DECODED_BLOCK_SIZE = 48; - /** - * Size of a PKM file header, in bytes. - */ - public static final int ETC_PKM_HEADER_SIZE = 16; - /** * Accepted by the internalformat parameter of glCompressedTexImage2D. */ public static final int ETC1_RGB8_OES = 0x8D64; - short etc1_byte; - int etc1_bool; - /* unsigned */long etc1_uint32; - - - static short convert4To8(int b) { - int c = b & 0xf; - return (short) ((c << 4) | c); - } - - static short convert4To8(long b) { - long c = b & 0xf; - return (short) ((c << 4) | c); - } - - static short convert5To8(int b) { - int c = b & 0x1f; - return (short) ((c << 3) | (c >> 2)); - } - - static short convert5To8(long b) { - long c = b & 0x1f; - return (short) ((c << 3) | (c >> 2)); - } - - static short convert6To8(int b) { - int c = b & 0x3f; - return (short) ((c << 2) | (c >> 4)); - } - - static short convert6To8(long b) { - long c = b & 0x3f; - return (short) ((c << 2) | (c >> 4)); - } - /** * Return the size of the encoded image data (does not include size of PKM * header). @@ -92,47 +53,42 @@ public static int getEncodedDataSize(int width, int height) { * * y + redOffset; pOut - pointer to encoded data. Must be large enough to * store entire encoded image. * @param script + * @param containMipmaps */ - public static int encodeImage(RenderScript rs, ScriptC_etc1compressor script, ByteBuffer pIn, int width, int height, - int pixelSize, int stride, ByteBuffer compressedImage) { + public static int encodeImage(RenderScript rs, ScriptC_etc1compressor script, Allocation aIn, int width, int height, + int pixelSize, int stride, ByteBuffer compressedImage, boolean containMipmaps) { long tInitArray = java.lang.System.currentTimeMillis(); script.set_height(height); script.set_width(width); + script.set_containMipmaps(containMipmaps); + script.set_pixelSize(pixelSize); - if (pixelSize < 2 || pixelSize > 3) { - System.out.println("unsupported pixelSize"); + if (pixelSize < 2 || pixelSize > 4) { return -1; } - - // int iOut = 0; - int size = Math.max(width * height / (DECODED_BLOCK_SIZE / 3), 1); + // int iOut = 0; - Allocation p00 = Allocation.createSized(rs, Element.U8(rs), width * height * pixelSize); + int size = Math.max(aIn.getBytesSize() / ((DECODED_BLOCK_SIZE/3)*pixelSize), 1); Allocation aout = Allocation.createSized(rs, Element.U16_4(rs), size); tInitArray = java.lang.System.currentTimeMillis() - tInitArray; - System.out.println("tInitArray : "+tInitArray+" ms"); - - long tFillAlloc = java.lang.System.currentTimeMillis(); - p00.copyFrom(pIn.array()); - - script.bind_pInA(p00); + //System.out.println("tInitArray : "+tInitArray+" ms"); + long tFillAlloc = java.lang.System.currentTimeMillis(); + script.bind_pInA(aIn); tFillAlloc = java.lang.System.currentTimeMillis() - tFillAlloc; - System.out.println("tFillAlloc : "+tFillAlloc+" ms"); + //System.out.println("tFillAlloc : "+tFillAlloc+" ms"); long tExec = java.lang.System.currentTimeMillis(); script.forEach_root(aout); tExec = java.lang.System.currentTimeMillis() - tExec; - System.out.println("tExec : "+tExec+" ms"); + //System.out.println("tExec : "+tExec+" ms"); long tFillOut = java.lang.System.currentTimeMillis(); - p00.destroy(); - short[] arrayOut3Temp = new short[4*size]; aout.copyTo(arrayOut3Temp); aout.destroy(); @@ -144,82 +100,14 @@ public static int encodeImage(RenderScript rs, ScriptC_etc1compressor script, By aout2.destroy(); tFillOut = java.lang.System.currentTimeMillis() - tFillOut; - System.out.println("tFillOut : "+tFillOut+" ms"); - compressedImage.position(0); + compressedImage.rewind(); + + //System.out.println("tFillOut : "+tFillOut+" ms"); + return 0; } - - static final byte kMagic[] = { 'P', 'K', 'M', ' ', '1', '0' }; - - static final int ETC1_PKM_FORMAT_OFFSET = 6; - static final int ETC1_PKM_ENCODED_WIDTH_OFFSET = 8; - static final int ETC1_PKM_ENCODED_HEIGHT_OFFSET = 10; - static final int ETC1_PKM_WIDTH_OFFSET = 12; - static final int ETC1_PKM_HEIGHT_OFFSET = 14; - - static final int ETC1_RGB_NO_MIPMAPS = 0; - - static void writeBEUint16(ByteBuffer header, int iOut, int data) { - header.position(iOut); - header.put((byte) (data >> 8)); - header.put((byte) data); - } - - static int readBEUint16(ByteBuffer headerBuffer, int iIn) { - return (headerBuffer.get(iIn) << 8) | headerBuffer.get(iIn + 1); - } - - // Format a PKM header - - public static void formatHeader(ByteBuffer header, int width, int height) { - header.put(kMagic); - int encodedWidth = (width + 3) & ~3; - int encodedHeight = (height + 3) & ~3; - writeBEUint16(header, ETC1_PKM_FORMAT_OFFSET, ETC1_RGB_NO_MIPMAPS); - writeBEUint16(header, ETC1_PKM_ENCODED_WIDTH_OFFSET, encodedWidth); - writeBEUint16(header, ETC1_PKM_ENCODED_HEIGHT_OFFSET, encodedHeight); - writeBEUint16(header, ETC1_PKM_WIDTH_OFFSET, width); - writeBEUint16(header, ETC1_PKM_HEIGHT_OFFSET, height); - } - - // Check if a PKM header is correctly formatted. - - public static boolean isValid(ByteBuffer headerBuffer) { - if (memcmp(headerBuffer, kMagic, kMagic.length)) { - return false; - } - int format = readBEUint16(headerBuffer, ETC1_PKM_FORMAT_OFFSET); - int encodedWidth = readBEUint16(headerBuffer, - ETC1_PKM_ENCODED_WIDTH_OFFSET); - int encodedHeight = readBEUint16(headerBuffer, - ETC1_PKM_ENCODED_HEIGHT_OFFSET); - int width = readBEUint16(headerBuffer, ETC1_PKM_WIDTH_OFFSET); - int height = readBEUint16(headerBuffer, ETC1_PKM_HEIGHT_OFFSET); - return format == ETC1_RGB_NO_MIPMAPS && encodedWidth >= width - && encodedWidth - width < 4 && encodedHeight >= height - && encodedHeight - height < 4; - } - - static boolean memcmp(ByteBuffer headerBuffer, byte[] b, int lenght) { - for (int i = 0; i < lenght; i++) { - if (headerBuffer.get(i) != b[i]) { - return true; - } - } - return false; - } - - // Read the image width from a PKM header - - public static int getWidth(ByteBuffer pHeader) { - return readBEUint16(pHeader, ETC1_PKM_WIDTH_OFFSET); - } - - // Read the image height from a PKM header - - public static int getHeight(ByteBuffer pHeader) { - return readBEUint16(pHeader, ETC1_PKM_HEIGHT_OFFSET); - } + + } diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java index b3bfece..0d2bf21 100644 --- a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java @@ -8,9 +8,10 @@ import java.nio.ByteOrder; import nicastel.renderscripttexturecompressor.etc1.rs.ScriptC_etc1compressor; -import android.graphics.Bitmap; import android.opengl.ETC1; import android.opengl.GLES10; +import android.support.v8.renderscript.Allocation; +import android.support.v8.renderscript.Element; import android.support.v8.renderscript.RenderScript; /** @@ -144,19 +145,19 @@ public static ETC1Texture createTexture(InputStream input) throws IOException { int height = 0; byte[] ioBuffer = new byte[4096]; { - if (input.read(ioBuffer, 0, RsETC1.ETC_PKM_HEADER_SIZE) != RsETC1.ETC_PKM_HEADER_SIZE) { + if (input.read(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE) != ETC1.ETC_PKM_HEADER_SIZE) { throw new IOException("Unable to read PKM file header."); } - ByteBuffer headerBuffer = ByteBuffer.allocateDirect(RsETC1.ETC_PKM_HEADER_SIZE) + ByteBuffer headerBuffer = ByteBuffer.allocateDirect(ETC1.ETC_PKM_HEADER_SIZE) .order(ByteOrder.nativeOrder()); - headerBuffer.put(ioBuffer, 0, RsETC1.ETC_PKM_HEADER_SIZE).position(0); - if (!RsETC1.isValid(headerBuffer)) { + headerBuffer.put(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE).position(0); + if (!ETC1.isValid(headerBuffer)) { throw new IOException("Not a PKM file."); } - width = RsETC1.getWidth(headerBuffer); - height = RsETC1.getHeight(headerBuffer); + width = ETC1.getWidth(headerBuffer); + height = ETC1.getHeight(headerBuffer); } - int encodedSize = RsETC1.getEncodedDataSize(width, height); + int encodedSize = ETC1.getEncodedDataSize(width, height); ByteBuffer dataBuffer = ByteBuffer.allocateDirect(encodedSize).order(ByteOrder.nativeOrder()); for (int i = 0; i < encodedSize; ) { int chunkSize = Math.min(ioBuffer.length, encodedSize - i); @@ -169,29 +170,6 @@ public static ETC1Texture createTexture(InputStream input) throws IOException { dataBuffer.position(0); return new ETC1Texture(width, height, dataBuffer); } - - /** - * Helper function that compresses an image into an ETC1Texture. - * @param bitmap - * @return the ETC1 texture. - */ - public static ETC1Texture compressBitmap(RenderScript rs, ScriptC_etc1compressor script, Bitmap bitmap){ - int encodedImageSize = RsETC1.getEncodedDataSize(bitmap.getWidth(), bitmap.getHeight()); - System.out.println("encodedImageSize : "+encodedImageSize); - ByteBuffer compressedImage = ByteBuffer.allocateDirect(encodedImageSize). - order(ByteOrder.nativeOrder()); - - ByteBuffer buffer = ByteBuffer.allocateDirect( - bitmap.getRowBytes() * bitmap.getHeight()).order( - ByteOrder.nativeOrder()); - bitmap.copyPixelsToBuffer(buffer); - buffer.rewind(); - - RsETC1.encodeImage(rs, script, buffer, bitmap.getWidth(), bitmap.getHeight(), bitmap.getByteCount(), bitmap.getByteCount() * bitmap.getWidth(), compressedImage); - - compressedImage.rewind(); - return new ETC1Texture(bitmap.getWidth(), bitmap.getHeight(), compressedImage); - } /** * Helper function that compresses an image into an ETC1Texture. @@ -207,10 +185,15 @@ public static ETC1Texture compressTexture(RenderScript rs, ScriptC_etc1compresso System.out.println("encodedImageSize : "+encodedImageSize); ByteBuffer compressedImage = ByteBuffer.allocateDirect(encodedImageSize). order(ByteOrder.nativeOrder()); + Allocation p00 = Allocation.createSized(rs, Element.U8(rs), width * height * pixelSize); + p00.copyFrom(((ByteBuffer)input).array()); + + // TODO : there is a bug in the android sdk : + // ETC1.encodeImage((ByteBuffer) input, width, height, 3, stride, compressedImage); should be + RsETC1.encodeImage(rs, script, p00, width, height, pixelSize, stride, compressedImage, false); + p00.destroy(); - RsETC1.encodeImage(rs, script, (ByteBuffer) input, width, height, pixelSize, stride, compressedImage); - - compressedImage.position(0); + compressedImage.rewind(); return new ETC1Texture(width, height, compressedImage); } @@ -228,12 +211,12 @@ public static void writeTexture(ETC1Texture texture, OutputStream output) throws try { int width = texture.getWidth(); int height = texture.getHeight(); - ByteBuffer header = ByteBuffer.allocateDirect(RsETC1.ETC_PKM_HEADER_SIZE).order(ByteOrder.nativeOrder()); - RsETC1.formatHeader(header, width, height); + ByteBuffer header = ByteBuffer.allocateDirect(ETC1.ETC_PKM_HEADER_SIZE).order(ByteOrder.nativeOrder()); + ETC1.formatHeader(header, width, height); header.position(0); byte[] ioBuffer = new byte[4096]; - header.get(ioBuffer, 0, RsETC1.ETC_PKM_HEADER_SIZE); - output.write(ioBuffer, 0, RsETC1.ETC_PKM_HEADER_SIZE); + header.get(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE); + output.write(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE); while (dataBuffer.remaining()>0) { int chunkSize = Math.min(ioBuffer.length, dataBuffer.remaining()); dataBuffer.get(ioBuffer, 0, chunkSize); diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs index 214b0f8..f9bd686 100644 --- a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs @@ -514,21 +514,40 @@ void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 inMask, etc1_byte* pOut uchar * pInA; // uchar3 uint32_t height; uint32_t width; +uint32_t pixelSize; +bool containMipmaps; -static etc1_uint32 pullBlockAndMask_from_565Raster(uint32_t bn, const etc1_byte* pIn, uint32_t height, uint32_t width, etc1_byte* block) { +static etc1_uint32 pullBlockAndMask_from_Raster(uint32_t pixelSize, uint32_t bn, const etc1_byte* pIn, uint32_t height, uint32_t width, etc1_byte* block, bool containMipmaps) { static const unsigned short kYMask[] = { 0x0, 0xf, 0xff, 0xfff, 0xffff }; static const unsigned short kXMask[] = { 0x0, 0x1111, 0x3333, 0x7777, 0xffff }; - static const int pixelSize = 2; - - etc1_uint32 encodedWidth = (width + 3) & ~3; - etc1_uint32 encodedHeight = (height + 3) & ~3; + + etc1_uint32 mask = 0; + + uint32_t bnMP = bn; + uint32_t widthMP = width ; + uint32_t heightMP = height ; + const etc1_byte* pInMP = pIn; + + if(containMipmaps) { + // mimaplevel to compress : recursive + while( bnMP > widthMP * heightMP / 16) { + // mimaplevel to compress : recursive + bnMP = bnMP - (widthMP * heightMP / 16); + pInMP = pInMP + widthMP * heightMP * 2; + widthMP = widthMP / 2; + heightMP = heightMP / 2; + } + } + + etc1_uint32 encodedWidth = (widthMP + 3) & ~3; + etc1_uint32 encodedHeight = (widthMP + 3) & ~3; //rsDebug("encodedWidth", encodedWidth); //rsDebug("encodedHeight", encodedHeight); - int by = bn / (encodedWidth / 4); - int bx = bn - (by * (encodedWidth / 4)); + int by = bnMP / (encodedWidth / 4); + int bx = bnMP - (by * (encodedWidth / 4)); //rsDebug("bn", bn); //rsDebug("by", by); @@ -536,37 +555,44 @@ static etc1_uint32 pullBlockAndMask_from_565Raster(uint32_t bn, const etc1_byte* int yEnd=4; if(by == (encodedHeight/4)) { - yEnd = encodedHeight - height; + yEnd = encodedHeight - heightMP; } int ymask = kYMask[yEnd]; int xEnd=4; if(bx == (encodedWidth/4)) { - xEnd = encodedWidth - width; + xEnd = encodedWidth - widthMP; } - etc1_uint32 mask = ymask & kXMask[xEnd]; + mask = ymask & kXMask[xEnd]; - int stride = pixelSize * width; + int stride = pixelSize * widthMP; int x = bx * 4; int y = by * 4; for (int cy = 0; cy < yEnd; cy++) { etc1_byte* q = block + (cy * 4) * 3; - const etc1_byte* p = pIn + pixelSize * x + stride * (y + cy); - for (int cx = 0; cx < xEnd; cx++) { - int pixel = (p[1] << 8) | p[0]; - //rsDebug("pixel", pixel); - //rsDebug("pixelR", convert5To8(pixel >> 11)); - //rsDebug("pixelG", convert6To8(pixel >> 5)); - //rsDebug("pixelB", convert5To8(pixel)); - *q++ = convert5To8(pixel >> 11); - *q++ = convert6To8(pixel >> 5); - *q++ = convert5To8(pixel); - p += pixelSize; + const etc1_byte* p = pInMP + pixelSize * x + stride * (y + cy); + for (int cx = 0; cx < xEnd; cx++) { + if(pixelSize == 2) { + // RGB 565 + int pixel = (p[1] << 8) | p[0]; + *q++ = convert5To8(pixel >> 11); + *q++ = convert6To8(pixel >> 5); + *q++ = convert5To8(pixel); + p += pixelSize; + } else { + // ARGB 8888 + // alpha p[3]; + *q++ = p[0]; + *q++ = p[1]; + *q++ = p[2]; + p += pixelSize; + } } } - return mask; + + return mask; } static etc1_uint32 pullBlockAndMask_from_DXT3(uint32_t bn, const etc1_byte* pIn, uint32_t height, uint32_t width, etc1_byte* block) { @@ -587,7 +613,7 @@ ushort4 __attribute__((kernel)) root(uint32_t x) { // R, G, B. Byte (3 * (x + 4 * y) is the R value of pixel (x, y) //rsDebug("pInA", pInA); - etc1_uint32 amask = pullBlockAndMask_from_565Raster(x, pInA, height, width, block); + etc1_uint32 amask = pullBlockAndMask_from_Raster(pixelSize, x, pInA, height, width, block, containMipmaps); //rsDebug("mask",amask); //for (int i = 0; i < 48; i++) { // rsDebug("pixel",block[i]); diff --git a/WorldWindowApplicationSample/src/config/Earth/BlackMarbleMapserverEtc1Test.xml b/WorldWindowApplicationSample/src/config/Earth/BlackMarbleMapserverEtc1Test.xml new file mode 100644 index 0000000..022499f --- /dev/null +++ b/WorldWindowApplicationSample/src/config/Earth/BlackMarbleMapserverEtc1Test.xml @@ -0,0 +1,51 @@ + + + + + + + blackmarble + + http://ec2-54-85-182-200.compute-1.amazonaws.com/mywms + http://ec2-54-85-182-200.compute-1.amazonaws.com/mywms + blackmarble + + + blackmarble + image/dds; format=ETC1 + + image/dds; format=ETC1 + + .dds + + + + + + + + + + + + + + + + + + + + + + + + 2 + 1000 + diff --git a/WorldWindowApplicationSample/src/config/worldwind.layers2.xml b/WorldWindowApplicationSample/src/config/worldwind.layers2.xml index 6c10de6..3c73fc7 100644 --- a/WorldWindowApplicationSample/src/config/worldwind.layers2.xml +++ b/WorldWindowApplicationSample/src/config/worldwind.layers2.xml @@ -23,6 +23,9 @@ + widthMP * heightMP / 16) { + // mimaplevel to compress : recursive + bnMP = bnMP - (widthMP * heightMP / 16); + pInMP = pInMP + widthMP * heightMP * 2; + widthMP = widthMP / 2; + heightMP = heightMP / 2; + } + } + + etc1_uint32 encodedWidth = (widthMP + 3) & ~3; + etc1_uint32 encodedHeight = (widthMP + 3) & ~3; //rsDebug("encodedWidth", encodedWidth); //rsDebug("encodedHeight", encodedHeight); - int by = bn / (encodedWidth / 4); - int bx = bn - (by * (encodedWidth / 4)); + int by = bnMP / (encodedWidth / 4); + int bx = bnMP - (by * (encodedWidth / 4)); //rsDebug("bn", bn); //rsDebug("by", by); @@ -536,37 +555,44 @@ static etc1_uint32 pullBlockAndMask_from_565Raster(uint32_t bn, const etc1_byte* int yEnd=4; if(by == (encodedHeight/4)) { - yEnd = encodedHeight - height; + yEnd = encodedHeight - heightMP; } int ymask = kYMask[yEnd]; int xEnd=4; if(bx == (encodedWidth/4)) { - xEnd = encodedWidth - width; + xEnd = encodedWidth - widthMP; } - etc1_uint32 mask = ymask & kXMask[xEnd]; + mask = ymask & kXMask[xEnd]; - int stride = pixelSize * width; + int stride = pixelSize * widthMP; int x = bx * 4; int y = by * 4; for (int cy = 0; cy < yEnd; cy++) { etc1_byte* q = block + (cy * 4) * 3; - const etc1_byte* p = pIn + pixelSize * x + stride * (y + cy); - for (int cx = 0; cx < xEnd; cx++) { - int pixel = (p[1] << 8) | p[0]; - //rsDebug("pixel", pixel); - //rsDebug("pixelR", convert5To8(pixel >> 11)); - //rsDebug("pixelG", convert6To8(pixel >> 5)); - //rsDebug("pixelB", convert5To8(pixel)); - *q++ = convert5To8(pixel >> 11); - *q++ = convert6To8(pixel >> 5); - *q++ = convert5To8(pixel); - p += pixelSize; + const etc1_byte* p = pInMP + pixelSize * x + stride * (y + cy); + for (int cx = 0; cx < xEnd; cx++) { + if(pixelSize == 2) { + // RGB 565 + int pixel = (p[1] << 8) | p[0]; + *q++ = convert5To8(pixel >> 11); + *q++ = convert6To8(pixel >> 5); + *q++ = convert5To8(pixel); + p += pixelSize; + } else { + // ARGB 8888 + // alpha p[3]; + *q++ = p[0]; + *q++ = p[1]; + *q++ = p[2]; + p += pixelSize; + } } } - return mask; + + return mask; } static etc1_uint32 pullBlockAndMask_from_DXT3(uint32_t bn, const etc1_byte* pIn, uint32_t height, uint32_t width, etc1_byte* block) { @@ -587,7 +613,7 @@ ushort4 __attribute__((kernel)) root(uint32_t x) { // R, G, B. Byte (3 * (x + 4 * y) is the R value of pixel (x, y) //rsDebug("pInA", pInA); - etc1_uint32 amask = pullBlockAndMask_from_565Raster(x, pInA, height, width, block); + etc1_uint32 amask = pullBlockAndMask_from_Raster(pixelSize, x, pInA, height, width, block, containMipmaps); //rsDebug("mask",amask); //for (int i = 0; i < 48; i++) { // rsDebug("pixel",block[i]); From 294526b6d21f7bb3fc12fb7eeeac1dcbd178a278 Mon Sep 17 00:00:00 2001 From: nicastel Date: Sun, 25 May 2014 22:21:54 +0200 Subject: [PATCH 23/23] Deactivation of mipmap generation by default Semaphore synchronisation to avoid paralell execution of renderscript (already parallelized) --- .../dds/ETC1Compressor.java | 76 +++++++++++-------- .../dds/ETC1DDSCompressor.java | 51 ++++++++++++- 2 files changed, 93 insertions(+), 34 deletions(-) diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1Compressor.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1Compressor.java index 6cf9714..2b3b638 100644 --- a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1Compressor.java +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1Compressor.java @@ -13,7 +13,7 @@ import gov.nasa.worldwind.util.dds.DXTCompressor; import java.nio.ByteBuffer; -import java.nio.ByteOrder; +import java.util.concurrent.Semaphore; import nicastel.renderscripttexturecompressor.etc1.rs.RsETC1; import nicastel.renderscripttexturecompressor.etc1.rs.ScriptC_etc1compressor; @@ -29,6 +29,8 @@ */ public class ETC1Compressor implements DXTCompressor { + Semaphore semaphore = new Semaphore(1); + public ETC1Compressor() { } @@ -37,6 +39,8 @@ public int getDXTFormat() { return ETCConstants.D3DFMT_ETC1; } + + public int getCompressedSize(Bitmap image, DXTCompressionAttributes attributes) { @@ -86,39 +90,45 @@ public void compressImage(Bitmap image, DXTCompressionAttributes attributes, throw new IllegalArgumentException(message); } - // TODO -// ByteBuffer bufferIn = ByteBuffer.allocateDirect( -// image.getRowBytes() * image.getHeight()).order( -// ByteOrder.nativeOrder()); -// image.copyPixelsToBuffer(bufferIn); -// bufferIn.rewind(); - - MipmapControl control = MipmapControl.MIPMAP_NONE; - int usage = Allocation.USAGE_SHARED; - if(attributes.isBuildMipmaps()) { - // Needs an ARGB 8888 Bitmap as input - control = MipmapControl.MIPMAP_FULL; - usage = Allocation.USAGE_SCRIPT; - - } - - Allocation alloc = Allocation.createFromBitmap(rs, image, control, usage); - alloc.generateMipmaps(); - - int pixelSize = image.getRowBytes()/image.getWidth(); - - int encodedImageSize = Math.max(alloc.getBytesSize() / ((RsETC1.DECODED_BLOCK_SIZE/3)*pixelSize), 1)*8; - //System.out.println("encodedImageSize : "+encodedImageSize); - - ByteBuffer bufferOut = ByteBuffer.allocateDirect(encodedImageSize); - - RsETC1.encodeImage(rs, script, alloc, image.getWidth(), image.getHeight(), pixelSize, image.getRowBytes(), bufferOut, attributes.isBuildMipmaps()); - - alloc.destroy(); + try { + + + MipmapControl control = MipmapControl.MIPMAP_NONE; + int usage = Allocation.USAGE_SHARED; + if(attributes.isBuildMipmaps()) { + // Needs an ARGB 8888 Bitmap as input + control = MipmapControl.MIPMAP_FULL; + usage = Allocation.USAGE_SCRIPT; + + } + + Allocation alloc = Allocation.createFromBitmap(rs, image, control, usage); + alloc.generateMipmaps(); + + int pixelSize = image.getRowBytes()/image.getWidth(); + + int encodedImageSize = Math.max(alloc.getBytesSize() / ((RsETC1.DECODED_BLOCK_SIZE/3)*pixelSize), 1)*8; + //System.out.println("encodedImageSize : "+encodedImageSize); + + ByteBuffer bufferOut = ByteBuffer.allocateDirect(encodedImageSize); + + semaphore.acquire(); + + RsETC1.encodeImage(rs, script, alloc, image.getWidth(), image.getHeight(), pixelSize, image.getRowBytes(), bufferOut, attributes.isBuildMipmaps()); + alloc.destroy(); + + semaphore.release(); + + bufferOut.rewind(); + + buffer.put(bufferOut); + + + } catch (InterruptedException e) { + e.printStackTrace(); + } - bufferOut.rewind(); - - buffer.put(bufferOut); + } protected ColorBlockExtractor getColorBlockExtractor(Bitmap image) diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java index 6e81060..6755026 100644 --- a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java @@ -97,7 +97,53 @@ public static java.nio.ByteBuffer compressImageBuffer(java.nio.ByteBuffer imageB throw new IllegalArgumentException(message); } - return compressImageBuffer(imageBuffer, DDSCompressor.getDefaultCompressionAttributes()); + return compressImageBuffer(imageBuffer, ETC1DDSCompressor.getDefaultCompressionAttributes()); + } + + /** + * Returns the default compression attributes. The default DXT compression attributes are defined as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
AttributeValue
Build Mipmapstrue
Premultiply Alphatrue
DXT FormatLet DDSCompressor choose optimal format.
Enable DXT1 Alphafalse
DXT1 Alpha Threshold128
Compression AlgorithmEuclidean Distance
+ * + * @return the default compression attributes. + */ + public static DXTCompressionAttributes getDefaultCompressionAttributes() { + DXTCompressionAttributes attributes = new DXTCompressionAttributes(); + + // TODO solve mipmap corruption bug + attributes.setBuildMipmaps(false); // Always build mipmaps. + + attributes.setPremultiplyAlpha(true); // Always create premultiplied alpha format files.. + attributes.setDXTFormat(ETCConstants.D3DFMT_ETC1); // Allow the DDSCompressor to choose the appropriate DXT format. + return attributes; } /** @@ -323,6 +369,9 @@ protected ByteBuffer doCompressImage(DXTCompressor compressor, Bitmap image, DXT header.setMipMapCount(1+maxLevel); } else { fileSize += compressor.getCompressedSize(image, attributes); + + header.setFlags(header.getFlags() | DDSConstants.DDSD_MIPMAPCOUNT); + header.setMipMapCount(1); } // Create a little endian buffer that holds the bytes of the DDS file.