aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/TexImageTransform.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/TexImageTransform.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/TexImageTransform.java342
1 files changed, 342 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/TexImageTransform.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/TexImageTransform.java
new file mode 100644
index 000000000..dde89eae6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/TexImageTransform.java
@@ -0,0 +1,342 @@
+/*
+ * 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.
+ */
+
+package com.android.ide.eclipse.gltrace.state.transforms;
+
+import com.android.ide.eclipse.gltrace.FileUtils;
+import com.android.ide.eclipse.gltrace.GLEnum;
+import com.android.ide.eclipse.gltrace.state.GLStringProperty;
+import com.android.ide.eclipse.gltrace.state.IGLProperty;
+import com.google.common.io.Files;
+import com.google.common.primitives.UnsignedBytes;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+import javax.imageio.ImageIO;
+
+/**
+ * {@link TexImageTransform} transforms the state to reflect the effect of a
+ * glTexImage2D or glTexSubImage2D GL call.
+ */
+public class TexImageTransform implements IStateTransform {
+ private static final String PNG_IMAGE_FORMAT = "PNG";
+ private static final String TEXTURE_FILE_PREFIX = "tex";
+ private static final String TEXTURE_FILE_SUFFIX = ".png";
+
+ private final IGLPropertyAccessor mAccessor;
+ private final File mTextureDataFile;
+
+ private final int mxOffset;
+ private final int myOffset;
+ private final int mWidth;
+ private final int mHeight;
+
+ private String mOldValue;
+ private String mNewValue;
+ private GLEnum mFormat;
+ private GLEnum mType;
+
+ /**
+ * Construct a texture image transformation.
+ * @param accessor accessor to obtain the GL state variable to modify
+ * @param textureData texture data passed in by the call. Could be null.
+ * @param format format of the source texture data
+ * @param xOffset x offset for the source data (used only in glTexSubImage2D)
+ * @param yOffset y offset for the source data (used only in glTexSubImage2D)
+ * @param width width of the texture
+ * @param height height of the texture
+ */
+ public TexImageTransform(IGLPropertyAccessor accessor, File textureData, GLEnum format,
+ GLEnum type, int xOffset, int yOffset, int width, int height) {
+ mAccessor = accessor;
+ mTextureDataFile = textureData;
+ mFormat = format;
+ mType = type;
+
+ mxOffset = xOffset;
+ myOffset = yOffset;
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public void apply(IGLProperty currentState) {
+ assert mOldValue == null : "Transform cannot be applied multiple times"; //$NON-NLS-1$
+
+ IGLProperty property = mAccessor.getProperty(currentState);
+ if (!(property instanceof GLStringProperty)) {
+ return;
+ }
+
+ GLStringProperty prop = (GLStringProperty) property;
+ mOldValue = prop.getStringValue();
+
+ // Applying texture transformations is a heavy weight process. So we perform
+ // it only once and save the result in a temporary file. The property is actually
+ // the path to the file.
+ if (mNewValue == null) {
+ try {
+ if (mOldValue == null) {
+ mNewValue = createTexture(mTextureDataFile, mWidth, mHeight);
+ } else {
+ mNewValue = updateTextureData(mOldValue, mTextureDataFile, mxOffset, myOffset,
+ mWidth, mHeight);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (RuntimeException e) {
+ throw e;
+ }
+ }
+
+ prop.setValue(mNewValue);
+ }
+
+ @Override
+ public void revert(IGLProperty state) {
+ if (mOldValue != null) {
+ IGLProperty property = mAccessor.getProperty(state);
+ property.setValue(mOldValue);
+ mOldValue = null;
+ }
+ }
+
+ @Override
+ public IGLProperty getChangedProperty(IGLProperty state) {
+ return mAccessor.getProperty(state);
+ }
+
+ /**
+ * Creates a texture of provided width and height. If the texture data file is provided,
+ * then the texture is initialized with the contents of that file, otherwise an empty
+ * image is created.
+ * @param textureDataFile path to texture data, could be null.
+ * @param width width of texture
+ * @param height height of texture
+ * @return path to cached texture
+ */
+ private String createTexture(File textureDataFile, int width, int height) throws IOException {
+ File f = FileUtils.createTempFile(TEXTURE_FILE_PREFIX, TEXTURE_FILE_SUFFIX);
+
+ BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
+
+ if (textureDataFile != null) {
+ byte[] initialData = Files.toByteArray(textureDataFile);
+ img.getRaster().setDataElements(0, 0, width, height,
+ formatSourceData(initialData, width, height));
+ }
+
+ ImageIO.write(img, PNG_IMAGE_FORMAT, f);
+
+ return f.getAbsolutePath();
+ }
+
+ /**
+ * Update part of an existing texture.
+ * @param currentImagePath current texture image.
+ * @param textureDataFile new data to update the current texture with
+ * @param xOffset x offset for the update region
+ * @param yOffset y offset for the update region
+ * @param width width of the update region
+ * @param height height of the update region
+ * @return path to the updated texture
+ */
+ private String updateTextureData(String currentImagePath, File textureDataFile,
+ int xOffset, int yOffset, int width, int height) throws IOException {
+ assert currentImagePath != null : "Attempt to update a null texture";
+
+ if (textureDataFile == null) {
+ // Do not perform any updates if we don't have the actual data.
+ return currentImagePath;
+ }
+
+ File f = FileUtils.createTempFile(TEXTURE_FILE_PREFIX, TEXTURE_FILE_SUFFIX);
+ BufferedImage image = null;
+ image = ImageIO.read(new File(currentImagePath));
+
+ byte[] subImageData = Files.toByteArray(textureDataFile);
+ image.getRaster().setDataElements(xOffset, yOffset, width, height,
+ formatSourceData(subImageData, width, height));
+ ImageIO.write(image, PNG_IMAGE_FORMAT, f);
+
+ return f.getAbsolutePath();
+ }
+
+ private byte[] formatSourceData(byte[] subImageData, int width, int height) {
+ if (mType != GLEnum.GL_UNSIGNED_BYTE) {
+ subImageData = unpackData(subImageData, mType);
+ }
+
+ switch (mFormat) {
+ case GL_RGBA:
+ // no conversions necessary
+ return subImageData;
+ case GL_RGB:
+ return addAlphaChannel(subImageData, width, height);
+ case GL_RED:
+ case GL_GREEN:
+ case GL_BLUE:
+ // GL_RED, GL_GREEN and GL_BLUE are all supposed to fill those respective
+ // channels, but we assume that the programmers intent was to use GL_ALPHA in order
+ // to overcome the issue that GL_ALPHA cannot be used with float data.
+ if (mType != GLEnum.GL_FLOAT) {
+ throw new RuntimeException();
+ } else {
+ // fall through - assume that it is GL_ALPHA
+ }
+ //$FALL-THROUGH$
+ case GL_ALPHA:
+ return addRGBChannels(subImageData, width, height);
+ case GL_LUMINANCE:
+ return createRGBAFromLuminance(subImageData, width, height);
+ case GL_LUMINANCE_ALPHA:
+ return createRGBAFromLuminanceAlpha(subImageData, width, height);
+ default:
+ throw new RuntimeException();
+ }
+ }
+
+ private byte[] unpackData(byte[] data, GLEnum type) {
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ return data;
+ case GL_UNSIGNED_SHORT_4_4_4_4:
+ return convertShortToUnsigned(data, 0xf000, 12, 0x0f00, 8, 0x00f0, 4, 0x000f, 0,
+ true);
+ case GL_UNSIGNED_SHORT_5_6_5:
+ return convertShortToUnsigned(data, 0xf800, 11, 0x07e0, 5, 0x001f, 0, 0, 0,
+ false);
+ case GL_UNSIGNED_SHORT_5_5_5_1:
+ return convertShortToUnsigned(data, 0xf800, 11, 0x07c0, 6, 0x003e, 1, 0x1, 0,
+ true);
+ case GL_FLOAT:
+ return convertFloatToUnsigned(data);
+ default:
+ return data;
+ }
+ }
+
+ private byte[] convertFloatToUnsigned(byte[] data) {
+ byte[] unsignedData = new byte[data.length];
+ ByteBuffer floatBuffer = ByteBuffer.wrap(data);
+ for (int i = 0; i < data.length / 4; i++) {
+ float v = floatBuffer.getFloat(i);
+ byte alpha = (byte)(v * 255);
+ unsignedData[i*4 + 3] = alpha;
+ }
+ return unsignedData;
+ }
+
+ private byte[] convertShortToUnsigned(byte[] shortData,
+ int rmask, int rshift,
+ int gmask, int gshift,
+ int bmask, int bshift,
+ int amask, int ashift,
+ boolean includeAlpha) {
+ int numChannels = includeAlpha ? 4 : 3;
+ byte[] unsignedData = new byte[(shortData.length/2) * numChannels];
+
+ for (int i = 0; i < (shortData.length / 2); i++) {
+ int hi = UnsignedBytes.toInt(shortData[i*2 + 0]);
+ int lo = UnsignedBytes.toInt(shortData[i*2 + 1]);
+
+ int x = hi << 8 | lo;
+
+ int r = (x & rmask) >>> rshift;
+ int g = (x & gmask) >>> gshift;
+ int b = (x & bmask) >>> bshift;
+ int a = (x & amask) >>> ashift;
+
+ unsignedData[i * numChannels + 0] = UnsignedBytes.checkedCast(r);
+ unsignedData[i * numChannels + 1] = UnsignedBytes.checkedCast(g);
+ unsignedData[i * numChannels + 2] = UnsignedBytes.checkedCast(b);
+
+ if (includeAlpha) {
+ unsignedData[i * numChannels + 3] = UnsignedBytes.checkedCast(a);
+ }
+ }
+
+ return unsignedData;
+ }
+
+ private byte[] addAlphaChannel(byte[] sourceData, int width, int height) {
+ assert sourceData.length == 3 * width * height; // should have R, G & B channels
+
+ byte[] data = new byte[4 * width * height];
+
+ for (int src = 0, dst = 0; src < sourceData.length; src += 3, dst += 4) {
+ data[dst + 0] = sourceData[src + 0]; // copy R byte
+ data[dst + 1] = sourceData[src + 1]; // copy G byte
+ data[dst + 2] = sourceData[src + 2]; // copy B byte
+ data[dst + 3] = 1; // add alpha = 1
+ }
+
+ return data;
+ }
+
+ private byte[] addRGBChannels(byte[] sourceData, int width, int height) {
+ assert sourceData.length == width * height; // should have a single alpha channel
+
+ byte[] data = new byte[4 * width * height];
+
+ for (int src = 0, dst = 0; src < sourceData.length; src++, dst += 4) {
+ data[dst + 0] = data[dst + 1] = data[dst + 2] = 0; // set R = G = B = 0
+ data[dst + 3] = sourceData[src]; // copy over alpha
+ }
+
+ return data;
+ }
+
+ private byte[] createRGBAFromLuminance(byte[] sourceData, int width, int height) {
+ assert sourceData.length == width * height; // should have a single luminance channel
+
+ byte[] data = new byte[4 * width * height];
+
+ for (int src = 0, dst = 0; src < sourceData.length; src++, dst += 4) {
+ int l = sourceData[src] * 3;
+ if (l > 255) { // clamp to 255
+ l = 255;
+ }
+
+ data[dst + 0] = data[dst + 1] = data[dst + 2] = (byte) l; // set R = G = B = L * 3
+ data[dst + 3] = 1; // set alpha = 1
+ }
+
+ return data;
+ }
+
+ private byte[] createRGBAFromLuminanceAlpha(byte[] sourceData, int width, int height) {
+ assert sourceData.length == 2 * width * height; // should have luminance & alpha channels
+
+ byte[] data = new byte[4 * width * height];
+
+ for (int src = 0, dst = 0; src < sourceData.length; src += 2, dst += 4) {
+ int l = sourceData[src] * 3;
+ if (l > 255) { // clamp to 255
+ l = 255;
+ }
+
+ data[dst + 0] = data[dst + 1] = data[dst + 2] = (byte) l; // set R = G = B = L * 3
+ data[dst + 3] = sourceData[src + 1]; // copy over alpha
+ }
+
+ return data;
+ }
+}