com.jme3.renderer.opengl.GLTracer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jme3-core Show documentation
Show all versions of jme3-core Show documentation
jMonkeyEngine is a 3-D game engine for adventurous Java developers
/*
* Copyright (c) 2009-2021 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.renderer.opengl;
import com.jme3.util.IntMap;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.util.HashMap;
/**
* Utility class that allows tracing of OpenGL calls generated by the engine.
*
* @author Kirill Vainer
*/
public final class GLTracer implements InvocationHandler {
private final Object obj;
private final IntMap constMap;
private static final HashMap> nonEnumArgMap = new HashMap>();
private static final String ANSI_RESET = "\u001B[0m";
private static final String ANSI_BRIGHT = "\u001B[1m";
private static final String ANSI_RED = "\u001B[31m";
private static final String ANSI_GREEN = "\u001B[32m";
private static final String ANSI_YELLOW = "\u001B[33m";
private static final String ANSI_BLUE = "\u001B[34m";
private static final String ANSI_MAGENTA = "\u001B[35m";
private static final String ANSI_CYAN = "\u001B[36m";
private static void noEnumArgs(String method, int... argSlots) {
IntMap argSlotsMap = new IntMap<>();
for (int argSlot : argSlots) {
argSlotsMap.put(argSlot, null);
}
nonEnumArgMap.put(method, argSlotsMap);
}
static {
noEnumArgs("glViewport", 0, 1, 2, 3);
noEnumArgs("glScissor", 0, 1, 2, 3);
noEnumArgs("glClear", 0);
noEnumArgs("glGetInteger", 1);
noEnumArgs("glGetString", 1);
noEnumArgs("glBindTexture", 1);
noEnumArgs("glPixelStorei", 1);
// noEnumArgs("glTexParameteri", 2);
noEnumArgs("glTexImage2D", 1, 3, 4, 5);
noEnumArgs("glTexImage3D", 1, 3, 4, 5, 6);
noEnumArgs("glTexSubImage2D", 1, 2, 3, 4, 5);
noEnumArgs("glTexSubImage3D", 1, 2, 3, 4, 5, 6, 7);
noEnumArgs("glCompressedTexImage2D", 1, 3, 4, 5);
noEnumArgs("glCompressedTexSubImage3D", 1, 2, 3, 4, 5, 6, 7);
noEnumArgs("glDeleteTextures", 0);
noEnumArgs("glReadPixels", 0, 1, 2, 3);
noEnumArgs("glBindBuffer", 1);
noEnumArgs("glEnableVertexAttribArray", 0);
noEnumArgs("glDisableVertexAttribArray", 0);
noEnumArgs("glVertexAttribPointer", 0, 1, 4, 5);
noEnumArgs("glVertexAttribDivisorARB", 0, 1);
noEnumArgs("glDrawRangeElements", 1, 2, 3, 5);
noEnumArgs("glDrawArrays", 1, 2);
noEnumArgs("glDeleteBuffers", 0);
noEnumArgs("glBindVertexArray", 0);
noEnumArgs("glGenVertexArrays", 0);
noEnumArgs("glBindFramebufferEXT", 1);
noEnumArgs("glBindRenderbufferEXT", 1);
noEnumArgs("glRenderbufferStorageEXT", 2, 3);
noEnumArgs("glRenderbufferStorageMultisampleEXT", 1, 3, 4);
noEnumArgs("glFramebufferRenderbufferEXT", 3);
noEnumArgs("glFramebufferTexture2DEXT", 3, 4);
noEnumArgs("glFramebufferTextureLayerEXT", 2, 3, 4);
noEnumArgs("glBlitFramebufferEXT", 0, 1, 2, 3, 4, 5, 6, 7, 8);
noEnumArgs("glCreateProgram", -1);
noEnumArgs("glCreateShader", -1);
noEnumArgs("glShaderSource", 0);
noEnumArgs("glCompileShader", 0);
noEnumArgs("glGetShader", 0);
noEnumArgs("glAttachShader", 0, 1);
noEnumArgs("glLinkProgram", 0);
noEnumArgs("glGetProgram", 0);
noEnumArgs("glUseProgram", 0);
noEnumArgs("glGetUniformLocation", 0, -1);
noEnumArgs("glUniformMatrix3", 0);
noEnumArgs("glUniformMatrix4", 0);
noEnumArgs("glUniform1i", 0, 1);
noEnumArgs("glUniform1f", 0);
noEnumArgs("glUniform2f", 0);
noEnumArgs("glUniform3f", 0);
noEnumArgs("glUniform4", 0);
noEnumArgs("glUniform4f", 0);
noEnumArgs("glGetAttribLocation", 0, -1);
noEnumArgs("glDetachShader", 0, 1);
noEnumArgs("glDeleteShader", 0);
noEnumArgs("glDeleteProgram", 0);
noEnumArgs("glBindFragDataLocation", 0, 1);
}
public GLTracer(Object obj, IntMap constMap) {
this.obj = obj;
this.constMap = constMap;
}
private static IntMap generateConstantMap(Class> ... classes) {
IntMap constMap = new IntMap<>();
for (Class> clazz : classes) {
for (Field field : clazz.getFields()) {
if (field.getType() == int.class) {
try {
int val = field.getInt(null);
String name = field.getName();
constMap.put(val, name);
} catch (IllegalArgumentException
| IllegalAccessException ex) {
}
}
}
}
// GL_ONE is more common than GL_TRUE (which is a boolean anyway)
constMap.put(1, "GL_ONE");
return constMap;
}
/**
* Creates a tracer implementation that wraps OpenGL ES 2.
*
* @param glInterface OGL object to wrap
* @param glInterfaceClasses The interface(s) to implement
* @return A tracer that implements the given interface
*/
public static Object createGlesTracer(Object glInterface, Class>... glInterfaceClasses) {
IntMap constMap = generateConstantMap(GL.class, GL2.class, GL3.class, GLFbo.class, GLExt.class);
return Proxy.newProxyInstance(
glInterface.getClass().getClassLoader(),
glInterfaceClasses,
new GLTracer(glInterface, constMap));
}
/**
* Creates a tracer implementation that wraps OpenGL 2+.
*
* @param glInterface OGL object to wrap
* @param glInterfaceClasses The interface(s) to implement
* @return A tracer that implements the given interface
*/
public static Object createDesktopGlTracer(Object glInterface, Class> ... glInterfaceClasses) {
IntMap constMap = generateConstantMap(GL2.class, GL3.class, GL4.class, GLFbo.class, GLExt.class);
return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(),
glInterfaceClasses,
new GLTracer(glInterface, constMap));
}
private void printStyle(String style, String string) {
System.out.print(style + string + ANSI_RESET);
}
private void print(String string) {
System.out.print(string);
}
private void printInt(int value) {
print(Integer.toString(value));
}
private void printEnum(int value) {
String enumName = constMap.get(value);
if (enumName != null) {
if (enumName.startsWith("GL_")) {
enumName = enumName.substring(3);
}
if (enumName.endsWith("_EXT") || enumName.endsWith("_ARB")) {
enumName = enumName.substring(0, enumName.length() - 4);
}
printStyle(ANSI_GREEN, enumName);
} else {
printStyle(ANSI_GREEN, "ENUM_" + Integer.toHexString(value));
}
}
private void printIntOrEnum(String method, int value, int argIndex) {
IntMap argSlotMap = nonEnumArgMap.get(method);
if (argSlotMap != null && argSlotMap.containsKey(argIndex)) {
printInt(value);
} else {
printEnum(value);
}
}
private void printNewLine() {
System.out.println();
}
private void printString(String value) {
if (value.length() > 150) {
value = value.substring(0, 150) + "...";
}
StringBuilder sb = new StringBuilder();
sb.append(ANSI_YELLOW);
sb.append("\"");
sb.append(ANSI_RESET);
for (String line : value.split("\n")) {
sb.append(ANSI_YELLOW);
sb.append(line.replaceAll("\0", "\\\\0"));
sb.append(ANSI_RESET);
sb.append("\n");
}
if (sb.length() > 1 && sb.charAt(sb.length() - 1) == '\n') {
sb.setLength(sb.length() - 1);
}
sb.append(ANSI_YELLOW);
sb.append("\"");
sb.append(ANSI_RESET);
print(sb.toString());
}
private void printBoolean(boolean bool) {
printStyle(ANSI_BLUE, bool ? "true" : "false");
}
private void printBuffer(Buffer buffer) {
StringBuilder sb = new StringBuilder();
sb.append(ANSI_MAGENTA);
if (buffer instanceof ByteBuffer) {
sb.append("byte");
} else if (buffer instanceof ShortBuffer) {
sb.append("short");
} else if (buffer instanceof CharBuffer) {
sb.append("char");
} else if (buffer instanceof FloatBuffer) {
sb.append("float");
} else if (buffer instanceof IntBuffer) {
sb.append("int");
} else if (buffer instanceof LongBuffer) {
sb.append("long");
} else if (buffer instanceof DoubleBuffer) {
sb.append("double");
} else {
throw new UnsupportedOperationException();
}
sb.append(ANSI_RESET);
sb.append("[");
if (buffer.position() == 0
&& buffer.limit() == buffer.capacity()) {
// Common case. Just print buffer size.
sb.append(buffer.capacity());
} else {
sb.append("pos=").append(buffer.position());
sb.append(" lim=").append(buffer.limit());
sb.append(" cap=").append(buffer.capacity());
}
sb.append("]");
print(sb.toString());
}
private void printMethodName(String methodName) {
if (methodName.startsWith("gl")) {
// GL calls which actually draw (as opposed to change state)
// will be printed in darker color
methodName = methodName.substring(2);
if (methodName.equals("Clear")
|| methodName.equals("DrawRangeElements")
|| methodName.equals("DrawElementsInstancedARB")) {
print(methodName);
} else {
if (methodName.endsWith("EXT")) {
methodName = methodName.substring(0, methodName.length() - 3);
}
printStyle(ANSI_BRIGHT, methodName);
}
} else if (methodName.equals("resetStats")) {
printStyle(ANSI_RED, "-- frame boundary --");
}
}
private void printArgsClear(int mask) {
boolean needAPipe = false;
print("(");
if ((mask & GL.GL_COLOR_BUFFER_BIT) != 0) {
printStyle(ANSI_GREEN, "COLOR_BUFFER_BIT");
needAPipe = true;
}
if ((mask & GL.GL_DEPTH_BUFFER_BIT) != 0) {
if (needAPipe) {
print(" | ");
}
printStyle(ANSI_GREEN, "DEPTH_BUFFER_BIT");
}
if ((mask & GL.GL_STENCIL_BUFFER_BIT) != 0) {
if (needAPipe) {
print(" | ");
}
printStyle(ANSI_GREEN, "STENCIL_BUFFER_BIT");
}
print(")");
}
private void printArgsGetInteger(Object[] args) {
print("(");
int param = (Integer)args[0];
IntBuffer ib = (IntBuffer) args[1];
printEnum(param);
print(", ");
printOut();
if (param == GL2.GL_DRAW_BUFFER || param == GL2.GL_READ_BUFFER) {
printEnum(ib.get(0));
} else {
printInt(ib.get(0));
}
print(")");
}
private void printArgsTexParameter(Object[] args) {
print("(");
int target = (Integer) args[0];
int param = (Integer) args[1];
int value = (Integer) args[2];
printEnum(target);
print(", ");
printEnum(param);
print(", ");
if (param == GL2.GL_TEXTURE_BASE_LEVEL
|| param == GL2.GL_TEXTURE_MAX_LEVEL) {
printInt(value);
} else {
printEnum(value);
}
print(")");
}
private void printOut() {
printStyle(ANSI_CYAN, "out=");
}
private void printResult(String methodName, Object result, Class> returnType) {
if (returnType != void.class) {
print(" = ");
if (result instanceof String) {
printString((String) result);
} else if (returnType == int.class) {
int val = (Integer) result;
printIntOrEnum(methodName, val, -1);
} else if (returnType == boolean.class) {
printBoolean((Boolean)result);
} else {
print(" = ???");
}
}
}
private void printNull() {
printStyle(ANSI_BLUE, "null");
}
private void printArgs(String methodName, Object[] args, Class>[] paramTypes) {
if (methodName.equals("glClear")) {
printArgsClear((Integer)args[0]);
return;
} else if (methodName.equals("glTexParameteri")) {
printArgsTexParameter(args);
return;
} else if (methodName.equals("glGetInteger")) {
printArgsGetInteger(args);
return;
}
if (args == null) {
print("()");
return;
}
print("(");
for (int i = 0; i < args.length; i++) {
if (paramTypes[i] == int.class) {
int val = (Integer)args[i];
printIntOrEnum(methodName, val, i);
} else if (paramTypes[i] == boolean.class) {
printBoolean((Boolean)args[i]);
} else if (paramTypes[i] == String.class) {
printString((String)args[i]);
} else if (paramTypes[i] == String[].class) {
String[] arr = (String[]) args[i];
if (arr.length == 1) {
printString(arr[0]);
} else {
print("string[" + arr.length + "]");
}
} else if (args[i] instanceof IntBuffer) {
IntBuffer buf = (IntBuffer) args[i];
if (buf.capacity() == 16) {
int val = buf.get(0);
printOut();
printIntOrEnum(methodName, val, i);
} else if (buf.capacity() == 1) {
printOut();
print(Integer.toString(buf.get(0)));
} else {
printBuffer(buf);
}
} else if (args[i] instanceof ByteBuffer) {
ByteBuffer bb = (ByteBuffer)args[i];
if (bb.capacity() == 250) {
printOut();
printBoolean(bb.get(0) != 0);
} else {
printBuffer(bb);
}
} else if (args[i] instanceof Buffer) {
printBuffer((Buffer)args[i]);
} else if (args[i] != null) {
print(args[i].toString());
} else {
printNull();
}
if (i != args.length - 1) {
System.out.print(", ");
}
}
print(")");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
printMethodName(methodName);
if (methodName.startsWith("gl")) {
try {
// Try to evaluate result first, so we can see output values.
Object result = method.invoke(obj, args);
printArgs(methodName, args, method.getParameterTypes());
printResult(methodName, result, method.getReturnType());
printNewLine();
return result;
} catch (Throwable ex) {
// Execution failed, print args anyway
// but output values will be incorrect.
printArgs(methodName, args, method.getParameterTypes());
printNewLine();
System.out.println("\tException occurred!");
System.out.println(ex.toString());
throw ex;
}
} else {
printNewLine();
return method.invoke(obj, args);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy