blob: 122695b59e74941f5b17f5c5170b33f363e8b1d8 [file] [log] [blame]
/*
** Copyright 2011, 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.glesv2debugger;
import com.android.glesv2debugger.DebuggerMessage.Message;
import com.android.glesv2debugger.DebuggerMessage.Message.DataType;
import com.android.glesv2debugger.DebuggerMessage.Message.Function;
import com.android.glesv2debugger.DebuggerMessage.Message.Prop;
import com.android.sdklib.util.SparseArray;
import com.android.sdklib.util.SparseIntArray;
import com.google.protobuf.ByteString;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
class Frame {
public final long filePosition;
private int callsCount;
final Context startContext;
private ArrayList<MessageData> calls = new ArrayList<MessageData>();
Frame(final Context context, final long filePosition) {
this.startContext = context.clone();
this.filePosition = filePosition;
}
void add(final MessageData msgData) {
calls.add(msgData);
}
void increaseCallsCount() {
callsCount++;
}
Context computeContext(final MessageData call) {
Context ctx = startContext.clone();
for (int i = 0; i < calls.size(); i++)
if (call == calls.get(i))
return ctx;
else
ctx.processMessage(calls.get(i).msg);
assert false;
return ctx;
}
int size() {
return callsCount;
}
MessageData get(final int i) {
return calls.get(i);
}
ArrayList<MessageData> get() {
return calls;
}
void unload() {
if (calls == null)
return;
calls.clear();
calls = null;
}
void load(final RandomAccessFile file) {
if (calls != null && calls.size() == callsCount)
return;
try {
Context ctx = startContext.clone();
calls = new ArrayList<MessageData>(callsCount);
final long oriPosition = file.getFilePointer();
file.seek(filePosition);
for (int i = 0; i < callsCount; i++) {
int len = file.readInt();
if (SampleView.targetByteOrder == ByteOrder.LITTLE_ENDIAN)
len = Integer.reverseBytes(len);
final byte[] data = new byte[len];
file.read(data);
Message msg = Message.parseFrom(data);
ctx.processMessage(msg);
final MessageData msgData = new MessageData(Display.getCurrent(), msg, ctx);
calls.add(msgData);
}
file.seek(oriPosition);
} catch (IOException e) {
e.printStackTrace();
assert false;
}
}
}
class DebugContext {
boolean uiUpdate = false;
final int contextId;
Context currentContext;
private ArrayList<Frame> frames = new ArrayList<Frame>(128);
private Frame lastFrame;
private Frame loadedFrame;
private RandomAccessFile file;
DebugContext(final int contextId) {
this.contextId = contextId;
currentContext = new Context(contextId);
try {
file = new RandomAccessFile("0x" + Integer.toHexString(contextId) +
".gles2dbg", "rw");
} catch (FileNotFoundException e) {
e.printStackTrace();
assert false;
}
}
/** write message to file; if frame not null, then increase its call count */
void saveMessage(final Message msg, final RandomAccessFile file, Frame frame) {
synchronized (file) {
if (frame != null)
frame.increaseCallsCount();
final byte[] data = msg.toByteArray();
final ByteBuffer len = ByteBuffer.allocate(4);
len.order(SampleView.targetByteOrder);
len.putInt(data.length);
try {
if (SampleView.targetByteOrder == ByteOrder.BIG_ENDIAN)
file.writeInt(data.length);
else
file.writeInt(Integer.reverseBytes(data.length));
file.write(data);
} catch (IOException e) {
e.printStackTrace();
assert false;
}
}
}
/**
* Caches new Message, and formats into MessageData for current frame; this
* function is called exactly once for each new Message
*/
void processMessage(final Message newMsg) {
Message msg = newMsg;
if (msg.getFunction() == Function.SETPROP) {
// GL impl. consts should have been sent before any GL call messages
assert frames.size() == 0;
assert lastFrame == null;
assert msg.getProp() == Prop.GLConstant;
switch (GLEnum.valueOf(msg.getArg0())) {
case GL_MAX_VERTEX_ATTRIBS:
currentContext.serverVertex = new GLServerVertex(msg.getArg1());
break;
case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
currentContext.serverTexture = new GLServerTexture(currentContext,
msg.getArg1());
break;
default:
assert false;
return;
}
saveMessage(msg, file, null);
return;
}
if (lastFrame == null) {
// first real message after the GL impl. consts
synchronized (file) {
try {
lastFrame = new Frame(currentContext, file.getFilePointer());
} catch (IOException e) {
e.printStackTrace();
assert false;
}
}
synchronized (frames) {
frames.add(lastFrame);
}
assert loadedFrame == null;
loadedFrame = lastFrame;
}
currentContext.processMessage(msg);
if (msg.hasDataType() && msg.getDataType() == DataType.ReferencedImage) {
// decode referenced image so it doesn't rely on context later on
final byte[] referenced = MessageProcessor.lzfDecompressChunks(msg.getData());
currentContext.readPixelRef = MessageProcessor.decodeReferencedImage(
currentContext.readPixelRef, referenced);
final byte[] decoded = MessageProcessor.lzfCompressChunks(
currentContext.readPixelRef, referenced.length);
msg = msg.toBuilder().setDataType(DataType.NonreferencedImage)
.setData(ByteString.copyFrom(decoded)).build();
}
saveMessage(msg, file, lastFrame);
if (loadedFrame == lastFrame) {
// frame selected for view, so format MessageData
final MessageData msgData = new MessageData(Display.getCurrent(), msg, currentContext);
lastFrame.add(msgData);
uiUpdate = true;
}
if (msg.getFunction() != Function.eglSwapBuffers)
return;
synchronized (frames) {
if (loadedFrame != lastFrame)
lastFrame.unload();
try {
frames.add(lastFrame = new Frame(currentContext, file.getFilePointer()));
// file.getChannel().force(false);
uiUpdate = true;
} catch (IOException e) {
e.printStackTrace();
assert false;
}
}
return;
}
Frame getFrame(int index) {
synchronized (frames) {
Frame newFrame = frames.get(index);
if (loadedFrame != null && loadedFrame != lastFrame && newFrame != loadedFrame) {
loadedFrame.unload();
uiUpdate = true;
}
loadedFrame = newFrame;
synchronized (file) {
loadedFrame.load(file);
}
return loadedFrame;
}
}
int frameCount() {
synchronized (frames) {
return frames.size();
}
}
}
/** aggregate of GL states */
public class Context implements Cloneable {
public final int contextId;
public ArrayList<Context> shares = new ArrayList<Context>(); // self too
public GLServerVertex serverVertex;
public GLServerShader serverShader = new GLServerShader(this);
public GLServerState serverState = new GLServerState(this);
public GLServerTexture serverTexture;
byte[] readPixelRef = new byte[0];
public Context(int contextId) {
this.contextId = contextId;
shares.add(this);
}
@Override
public Context clone() {
try {
Context copy = (Context) super.clone();
// FIXME: context sharing list clone
copy.shares = new ArrayList<Context>(1);
copy.shares.add(copy);
if (serverVertex != null)
copy.serverVertex = serverVertex.clone();
copy.serverShader = serverShader.clone(copy);
copy.serverState = serverState.clone();
if (serverTexture != null)
copy.serverTexture = serverTexture.clone(copy);
// don't need to clone readPixelsRef, since referenced images
// are decoded when they are encountered
return copy;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
assert false;
return null;
}
}
/** mainly updating states */
public void processMessage(Message msg) {
if (serverVertex.process(msg))
return;
if (serverShader.processMessage(msg))
return;
if (serverState.processMessage(msg))
return;
if (serverTexture.processMessage(msg))
return;
}
}
class ContextViewProvider extends LabelProvider implements ITreeContentProvider,
ISelectionChangedListener {
Context context;
final SampleView sampleView;
ContextViewProvider(final SampleView sampleView) {
this.sampleView = sampleView;
}
@Override
public void dispose() {
}
@Override
public String getText(Object obj) {
if (obj == null)
return "null";
if (obj instanceof Entry) {
Entry entry = (Entry) obj;
String objStr = "null (or default)";
if (entry.obj != null) {
objStr = entry.obj.toString();
if (entry.obj instanceof Message)
objStr = MessageFormatter.format((Message) entry.obj, false);
}
return entry.name + " = " + objStr;
}
return obj.toString();
}
@Override
public Image getImage(Object obj) {
if (!(obj instanceof Entry))
return null;
final Entry entry = (Entry) obj;
if (!(entry.obj instanceof Message))
return null;
final Message msg = (Message) entry.obj;
switch (msg.getFunction()) {
case glTexImage2D:
case glTexSubImage2D:
case glCopyTexImage2D:
case glCopyTexSubImage2D: {
entry.image = new MessageData(Display.getCurrent(), msg, null).getImage();
if (entry.image == null)
return null;
return new Image(Display.getCurrent(), entry.image.getImageData().scaledTo(96, 96));
}
default:
return null;
}
}
@Override
public void selectionChanged(SelectionChangedEvent event) {
StructuredSelection selection = (StructuredSelection) event
.getSelection();
if (null == selection)
return;
final Object obj = selection.getFirstElement();
if (!(obj instanceof Entry))
return;
final Entry entry = (Entry) obj;
if (entry.image == null)
return;
sampleView.tabFolder.setSelection(sampleView.tabItemImage);
sampleView.canvas.setBackgroundImage(entry.image);
sampleView.canvas.redraw();
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
context = (Context) newInput;
}
class Entry {
String name;
Object obj;
Image image;
Entry(String name, Object obj) {
this.name = name;
this.obj = obj;
}
}
@Override
public Object[] getElements(Object inputElement) {
if (inputElement != context)
return null;
return getChildren(new Entry("Context", inputElement));
}
@Override
public Object[] getChildren(Object parentElement) {
if (!(parentElement instanceof Entry))
return null;
Entry entry = (Entry) parentElement;
ArrayList<Object> children = new ArrayList<Object>();
if (entry.obj == context.serverState.enableDisables) {
for (int i = 0; i < context.serverState.enableDisables.size(); i++) {
final int key = context.serverState.enableDisables.keyAt(i);
final int value = context.serverState.enableDisables.valueAt(i);
children.add(GLEnum.valueOf(key).name() + " = " + value);
}
} else if (entry.obj == context.serverState.integers) {
for (int i = 0; i < context.serverState.integers.size(); i++) {
final int key = context.serverState.integers.keyAt(i);
final Message val = context.serverState.integers.valueAt(i);
if (val != null)
children.add(GLEnum.valueOf(key).name() + " : " +
MessageFormatter.format(val, false));
else
children.add(GLEnum.valueOf(key).name() + " : default");
}
} else if (entry.obj == context.serverState.lastSetter) {
for (int i = 0; i < context.serverState.lastSetter.size(); i++) {
final int key = context.serverState.lastSetter.keyAt(i);
final Message msg = context.serverState.lastSetter.valueAt(i);
if (msg == null)
children.add(Function.valueOf(key).name() + " : default");
else
children.add(Function.valueOf(key).name() + " : "
+ MessageFormatter.format(msg, false));
}
} else if (entry.obj instanceof SparseArray) {
SparseArray<?> sa = (SparseArray<?>) entry.obj;
for (int i = 0; i < sa.size(); i++)
children.add(new Entry("[" + sa.keyAt(i) + "]", sa.valueAt(i)));
} else if (entry.obj instanceof Map) {
Set<?> set = ((Map<?, ?>) entry.obj).entrySet();
for (Object o : set) {
Map.Entry e = (Map.Entry) o;
children.add(new Entry(e.getKey().toString(), e.getValue()));
}
} else if (entry.obj instanceof SparseIntArray) {
SparseIntArray sa = (SparseIntArray) entry.obj;
for (int i = 0; i < sa.size(); i++)
children.add("[" + sa.keyAt(i) + "] = " + sa.valueAt(i));
} else if (entry.obj instanceof Collection) {
Collection<?> collection = (Collection<?>) entry.obj;
for (Object o : collection)
children.add(new Entry("[?]", o));
} else if (entry.obj.getClass().isArray()) {
for (int i = 0; i < Array.getLength(entry.obj); i++)
children.add(new Entry("[" + i + "]", Array.get(entry.obj, i)));
} else {
Field[] fields = entry.obj.getClass().getFields();
for (Field f : fields) {
try {
children.add(new Entry(f.getName(), f.get(entry.obj)));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return children.toArray();
}
@Override
public Object getParent(Object element) {
return null;
}
@Override
public boolean hasChildren(Object element) {
if (element == null)
return false;
if (!(element instanceof Entry))
return false;
Object obj = ((Entry) element).obj;
if (obj == null)
return false;
if (obj instanceof SparseArray)
return ((SparseArray<?>) obj).size() > 0;
else if (obj instanceof SparseIntArray)
return ((SparseIntArray) obj).size() > 0;
else if (obj instanceof Collection)
return ((Collection<?>) obj).size() > 0;
else if (obj instanceof Map)
return ((Map<?, ?>) obj).size() > 0;
else if (obj.getClass().isArray())
return Array.getLength(obj) > 0;
else if (obj instanceof Message)
return false;
else if (isPrimitive(obj))
return false;
else if (obj.getClass().equals(String.class))
return false;
else if (obj.getClass().equals(Message.class))
return false;
else if (obj instanceof GLEnum)
return false;
return obj.getClass().getFields().length > 0;
}
static boolean isPrimitive(final Object obj) {
final Class<? extends Object> c = obj.getClass();
if (c.isPrimitive())
return true;
if (c == Integer.class)
return true;
if (c == Boolean.class)
return true;
if (c == Float.class)
return true;
if (c == Short.class)
return true;
return false;
}
}