diff --git a/WorldWindAndroid/AndroidManifest.xml b/WorldWindAndroid/AndroidManifest.xml index c8da25c..1fe2db2 100644 --- a/WorldWindAndroid/AndroidManifest.xml +++ b/WorldWindAndroid/AndroidManifest.xml @@ -3,13 +3,17 @@ 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 36f1594..513bc8e 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-15 +target=android-19 android.library=true +renderscript.target=18 +renderscript.support.mode=true +sdk.buildtools=19.0.3 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 @@ + + + + diff --git a/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml b/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml index f5120c2..554d70a 100644 --- a/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml +++ b/WorldWindAndroid/src/config/Earth/BMNGWMSLayer.xml @@ -17,12 +17,15 @@ 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 - .png + .dds + diff --git a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml index bd2f448..3fdd8a4 100644 --- a/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml +++ b/WorldWindAndroid/src/config/Earth/LandsatI3WMSLayer.xml @@ -10,12 +10,15 @@ 26 03 2009 00:00:00 GMT Earth/NASA LandSat I3 WMS - image/png + image/jpeg + image/jpeg image/png image/dds - .png + .jpg + diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/WorldWindowGLSurfaceView.java b/WorldWindAndroid/src/gov/nasa/worldwind/WorldWindowGLSurfaceView.java index 42b8ada..f62422e 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.*; @@ -29,6 +34,8 @@ */ public class WorldWindowGLSurfaceView extends GLSurfaceView implements GLSurfaceView.Renderer, WorldWindow, WWObject { + protected static String glVersion; + protected WWObjectImpl wwo = new WWObjectImpl(this); protected SceneController sceneController; protected InputHandler inputHandler; @@ -42,7 +49,7 @@ public class WorldWindowGLSurfaceView extends GLSurfaceView implements GLSurface public WorldWindowGLSurfaceView(Context context) { super(context); - + try { this.init(null); @@ -74,7 +81,7 @@ public WorldWindowGLSurfaceView(Context context, AttributeSet attrs) public WorldWindowGLSurfaceView(Context context, EGLConfigChooser configChooser) { super(context); - + try { this.init(configChooser); @@ -162,9 +169,17 @@ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) // recognize. 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, 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 4540014..aa7dfe6 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 $ @@ -142,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) { @@ -155,9 +168,25 @@ protected static GpuTextureData fromStream(InputStream stream) if (data != null) return data; - stream.reset(); - - Bitmap bitmap = BitmapFactory.decodeStream(stream); + 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/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/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); diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/retrieve/AbstractRetrievalPostProcessor.java b/WorldWindAndroid/src/gov/nasa/worldwind/retrieve/AbstractRetrievalPostProcessor.java index 20c53ea..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; @@ -264,6 +273,7 @@ protected File getOutputFile() */ protected boolean overwriteExistingFile() { + // TODO return false; } @@ -635,11 +645,12 @@ protected ByteBuffer convertToDDS() throws IOException { ByteBuffer buffer; - Bitmap image = this.transformPixels(); - if (image != null) - buffer = DDSCompressor.compressImage(image); - else - buffer = DDSCompressor.compressImageBuffer(this.getRetriever().getBuffer()); + // TODO +// Bitmap image = this.transformPixels(); +// if (image != null) +// buffer = DDSCompressor.compressImage(image); +// else + buffer = ETC1DDSCompressor.compressImageBuffer(this.getRetriever().getBuffer()); return buffer; } 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; 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..f5dd18b 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSCompressor.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/dds/DDSCompressor.java @@ -10,8 +10,11 @@ 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.Bitmap.Config; import android.graphics.BitmapFactory; +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 +61,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.ARGB_8888; + Bitmap image = BitmapFactory.decodeStream(inputStream, null, opts); + if (image == null) { return null; } @@ -191,9 +198,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 +357,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(); } 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..1b87dee 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() { @@ -74,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); @@ -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) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java index ac08047..1603381 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMGpuTextureData.java @@ -5,18 +5,23 @@ */ 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 nicastel.renderscripttexturecompressor.etc1.rs.RsETC1Util.ETC1Texture; import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; -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.graphics.BitmapFactory.Options; +import android.opengl.ETC1Util; +import android.opengl.GLES20; /** * @author dcollins @@ -24,9 +29,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 fromPKMETC1CompressedData(ETC1Texture etctex, long estimatedMemorySize) { if (etctex == null || etctex.getHeight() == 0) { @@ -112,25 +131,32 @@ 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; + if(isDXTSupported || isETC1Supported) { + stream.mark(DEFAULT_MARK_LIMIT); + + DDSTextureReader ddsReader = new DDSTextureReader(); + data = ddsReader.read(stream); + if (data != null) + return data; - stream.reset(); + stream.reset(); + } - Bitmap bitmap = BitmapFactory.decodeStream(stream); + if(isETC1Supported) { + stream.mark(DEFAULT_MARK_LIMIT); + + PKMTextureReader pkmReader = new PKMTextureReader(); + data = pkmReader.read(stream); + if (data != null) + return data; + + stream.reset(); + } + + 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) diff --git a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMReader.java b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java similarity index 58% rename from WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMReader.java rename to WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java index 2422a3d..35f414c 100644 --- a/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMReader.java +++ b/WorldWindAndroid/src/gov/nasa/worldwind/util/pkm/PKMTextureReader.java @@ -3,32 +3,31 @@ 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.ETC1Util; -import android.opengl.ETC1Util.ETC1Texture; -public class PKMReader { +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 + texture.getHeight() * texture.getWidth() / 2; - return PKMGpuTextureData.fromETCCompressedData(texture, + return PKMGpuTextureData.fromPKMETC1CompressedData(texture, estimatedMemorySize); } else { 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; } } - } diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1Compressor.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1Compressor.java new file mode 100644 index 0000000..2b3b638 --- /dev/null +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1Compressor.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2012 United States Government as represented by the Administrator of the + * National Aeronautics and Space Administration. + * All Rights Reserved. + */ + +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.util.concurrent.Semaphore; + +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; + +/** + * @author nicastel + * @author dcollins + * @version $Id: DXT1Compressor.java 733 2012-09-02 17:15:09Z dcollins $ + */ +public class ETC1Compressor implements DXTCompressor +{ + Semaphore semaphore = new Semaphore(1); + + public ETC1Compressor() + { + } + + public int getDXTFormat() + { + return ETCConstants.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); + } + + 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(); + } + + + } + + protected ColorBlockExtractor getColorBlockExtractor(Bitmap image) + { + return new BasicColorBlockExtractor(image); + } +} diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java new file mode 100644 index 0000000..6755026 --- /dev/null +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETC1DDSCompressor.java @@ -0,0 +1,400 @@ +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, 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; + } + + /** + * 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); + + header.setFlags(header.getFlags() | DDSConstants.DDSD_MIPMAPCOUNT); + header.setMipMapCount(1); + } + + // 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 new file mode 100644 index 0000000..de301a9 --- /dev/null +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java @@ -0,0 +1,113 @@ +package nicastel.renderscripttexturecompressor.etc1.rs; + +import java.nio.ByteBuffer; + +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. + // 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. + // 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 pixel of a decoded block. + */ + public static final int DECODED_BLOCK_SIZE = 48; + + /** + * Accepted by the internalformat parameter of glCompressedTexImage2D. + */ + public static final int ETC1_RGB8_OES = 0x8D64; + + /** + * 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 + * @param containMipmaps + */ + 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 > 4) { + return -1; + } + + // int iOut = 0; + + 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(); + script.bind_pInA(aIn); + 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); + aout.destroy(); + + 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; + + compressedImage.rewind(); + + //System.out.println("tFillOut : "+tFillOut+" ms"); + + return 0; + } + + + +} 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..0d2bf21 --- /dev/null +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java @@ -0,0 +1,229 @@ +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.opengl.ETC1; +import android.opengl.GLES10; +import android.support.v8.renderscript.Allocation; +import android.support.v8.renderscript.Element; +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, ETC1.ETC_PKM_HEADER_SIZE) != ETC1.ETC_PKM_HEADER_SIZE) { + throw new IOException("Unable to read PKM file header."); + } + ByteBuffer headerBuffer = ByteBuffer.allocateDirect(ETC1.ETC_PKM_HEADER_SIZE) + .order(ByteOrder.nativeOrder()); + headerBuffer.put(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE).position(0); + if (!ETC1.isValid(headerBuffer)) { + throw new IOException("Not a PKM file."); + } + width = ETC1.getWidth(headerBuffer); + height = ETC1.getHeight(headerBuffer); + } + 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); + 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 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()); + 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(); + + compressedImage.rewind(); + 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(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, 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); + 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..f9bd686 --- /dev/null +++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs @@ -0,0 +1,643 @@ +/* + * 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; +uint32_t pixelSize; +bool containMipmaps; + +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 }; + + 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 = bnMP / (encodedWidth / 4); + int bx = bnMP - (by * (encodedWidth / 4)); + + //rsDebug("bn", bn); + //rsDebug("by", by); + //rsDebug("bx", bx); + + int yEnd=4; + if(by == (encodedHeight/4)) { + yEnd = encodedHeight - heightMP; + } + int ymask = kYMask[yEnd]; + + int xEnd=4; + if(bx == (encodedWidth/4)) { + xEnd = encodedWidth - widthMP; + } + mask = ymask & kXMask[xEnd]; + + 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 = 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; +} + +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_Raster(pixelSize, x, pInA, height, width, block, containMipmaps); + //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/AndroidManifest.xml b/WorldWindowApplicationSample/AndroidManifest.xml index 932775f..4977ef1 100644 --- a/WorldWindowApplicationSample/AndroidManifest.xml +++ b/WorldWindowApplicationSample/AndroidManifest.xml @@ -8,14 +8,19 @@ --> + android:versionCode="6" + android:versionName="1.0" > + - + + + + + diff --git a/WorldWindowApplicationSample/project.properties b/WorldWindowApplicationSample/project.properties index fddb90b..4e1f56f 100644 --- a/WorldWindowApplicationSample/project.properties +++ b/WorldWindowApplicationSample/project.properties @@ -11,5 +11,10 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-15 +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/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/Earth/GeoserverEtc1Test.xml b/WorldWindowApplicationSample/src/config/Earth/GeoserverEtc1Test.xml new file mode 100644 index 0000000..6e48142 --- /dev/null +++ b/WorldWindowApplicationSample/src/config/Earth/GeoserverEtc1Test.xml @@ -0,0 +1,51 @@ + + + + + + + ETC1 layer + + http://mygeoserver-nicastel.rhcloud.com/geoserver/wms + http://mygeoserver-nicastel.rhcloud.com/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..3c73fc7 100644 --- a/WorldWindowApplicationSample/src/config/worldwind.layers2.xml +++ b/WorldWindowApplicationSample/src/config/worldwind.layers2.xml @@ -19,8 +19,15 @@ 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; +uint32_t pixelSize; +bool containMipmaps; + +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 }; + + 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 = bnMP / (encodedWidth / 4); + int bx = bnMP - (by * (encodedWidth / 4)); + + //rsDebug("bn", bn); + //rsDebug("by", by); + //rsDebug("bx", bx); + + int yEnd=4; + if(by == (encodedHeight/4)) { + yEnd = encodedHeight - heightMP; + } + int ymask = kYMask[yEnd]; + + int xEnd=4; + if(bx == (encodedWidth/4)) { + xEnd = encodedWidth - widthMP; + } + mask = ymask & kXMask[xEnd]; + + 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 = 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; +} + +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_Raster(pixelSize, x, pInA, height, width, block, containMipmaps); + //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; +}