Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ Because some errors in user programs can cause the JVM to crash without a meanin

1. Download the `lwjglx-debug-1.0.0.jar` file from [https://www.lwjgl.org/browse/addons/lwjglx-debug](https://www.lwjgl.org/browse/addons/lwjglx-debug) or build this Maven project via the instructions in the 'Build' section below
2. Copy the `lwjglx-debug-1.0.0.jar` to any directory (henceforth called `<cwd>`)
3. Start your LWJGL3 application with the **JVM/VM argument** (_not_ program argument) `-javaagent:<cwd>/lwjglx-debug-1.0.0.jar`
1. when using the command line, it should look like: `java -javaagent:<cwd>/lwjglx-debug-1.0.0.jar -cp all/the/jars your.main.Class`
3. Start your LWJGL3 application with the **JVM/VM argument** (_not_ program argument) `-javaagent:<cwd>/lwjglx-debug-1.0.4.jar`
1. when using the command line, it should look like: `java -javaagent:<cwd>/lwjglx-debug-1.0.4.jar -cp all/the/jars your.main.Class`
2. when using Eclipse, right-click your class with the `main()` method, goto 'Run As > Run Configurations...' and on the 'Arguments' tab inside the 'VM Arguments:' field enter `-javaagent:<cwd>/lwjglx-debug-1.0.0.jar`

# Configuration
Expand All @@ -29,13 +29,13 @@ The following configuration properties are available to configure the library:

Examples:

* `java -javaagent:lwjglx-debug-1.0.0.jar=t ...` (generate a trace on stderr)
* `java -javaagent:lwjglx-debug-1.0.0.jar=t;o=trace.log` (generate a trace written to file `trace.log`)
* `java -javaagent:lwjglx-debug-1.0.0.jar=t;o=trace.log.zip` (generate a zip archive containing a single `trace.log` file)
* `java -javaagent:lwjglx-debug-1.0.0.jar=tn;o=trace.log` (generate a trace written to file `trace.log` and do not throw on GL errors)
* `java -javaagent:lwjglx-debug-1.0.0.jar=t;e=*GL20*,*GL11.glVertex3f` (generate a trace on stderr and exclude all methods from any class having `GL20` in its name, as well as exclude `glVertex3f` from any class ending with `GL11`)
* `java -javaagent:lwjglx-debug-1.0.4.jar=t ...` (generate a trace on stderr)
* `java -javaagent:lwjglx-debug-1.0.4.jar=t;o=trace.log` (generate a trace written to file `trace.log`)
* `java -javaagent:lwjglx-debug-1.0.4.jar=t;o=trace.log.zip` (generate a zip archive containing a single `trace.log` file)
* `java -javaagent:lwjglx-debug-1.0.4.jar=tn;o=trace.log` (generate a trace written to file `trace.log` and do not throw on GL errors)
* `java -javaagent:lwjglx-debug-1.0.4.jar=t;e=*GL20*,*GL11.glVertex3f` (generate a trace on stderr and exclude all methods from any class having `GL20` in its name, as well as exclude `glVertex3f` from any class ending with `GL11`)

# Build

1. `./mvnw package`
2. see target/lwjglx-debug-1.0.0.jar
2. see target/lwjglx-debug-1.0.4.jar
15 changes: 14 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.lwjglx</groupId>
<artifactId>lwjglx-debug</artifactId>
<version>1.0.3</version>
<version>1.0.4</version>
<name>LWJGLX/debug</name>
<description>Debug LWJGL3 OpenGL applications</description>
<inceptionYear>2017</inceptionYear>
Expand Down Expand Up @@ -414,6 +414,12 @@
<version>${lwjgl.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-sdl</artifactId>
<version>${lwjgl.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
Expand All @@ -435,5 +441,12 @@
<classifier>natives-${platform}</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-sdl</artifactId>
<version>${lwjgl.version}</version>
<classifier>natives-${platform}</classifier>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
71 changes: 54 additions & 17 deletions src/org/lwjglx/debug/InterceptClassGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,31 @@ of this software and associated documentation files (the "Software"), to deal
*/
package org.lwjglx.debug;

import static org.lwjglx.debug.Log.*;
import static org.lwjglx.debug.Properties.*;
import org.lwjglx.debug.ClassMetadata.MethodInfo;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.TraceClassVisitor;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.lwjglx.debug.ClassMetadata.MethodInfo;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.TraceClassVisitor;
import static org.lwjglx.debug.Log.debug;
import static org.lwjglx.debug.Properties.DEBUG;
import static org.lwjglx.debug.Properties.TRACE;
import static org.lwjglx.debug.Properties.VALIDATE;

class InterceptedCall {
private static final AtomicInteger counter = new AtomicInteger();
Expand Down Expand Up @@ -130,6 +134,20 @@ class InterceptClassGenerator implements Opcodes {

private static final Map<ClassKey, HashSet<Method>> declaredMethods = new ConcurrentHashMap<>();

private static final Set<String> GLFW_MAIN_THREAD_METHODS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"glfwInit", "glfwTerminate", "glfwCreateWindow", "glfwDefaultWindowHints", "glfwDestroyWindow", "glfwFocusWindow",
"glfwGetFramebufferSize", "glfwGetWindowAttrib", "glfwGetWindowFrameSize", "glfwGetWindowMonitor", "glfwGetWindowPos",
"glfwGetWindowSize", "glfwHideWindow", "glfwIconifyWindow", "glfwMaximizeWindow", "glfwPollEvents", "glfwRestoreWindow",
"glfwSetFramebufferSizeCallback", "glfwSetWindowAspectRatio", "glfwSetWindowCloseCallback", "glfwSetWindowFocusCallback",
"glfwSetWindowIcon", "glfwSetWindowIconifyCallback", "glfwSetWindowMonitor", "glfwSetWindowPos", "glfwSetWindowPosCallback",
"glfwSetWindowRefreshCallback", "glfwSetWindowSize", "glfwSetWindowSizeCallback", "glfwSetWindowSizeLimits",
"glfwSetWindowTitle", "glfwShowWindow", "glfwWaitEvents", "glfwWaitEventsTimeout", "glfwWindowHint"
)));

private static final Set<String> SDL_THREAD_SAFE_METHODS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"SDL_GL_MakeCurrent", "SDL_GL_GetCurrentContext", "SDL_GL_SwapWindow", "SDL_GL_DestroyContext"
)));

private static boolean isGLcall(InterceptedCall call) {
return (call.name.startsWith("gl") || call.name.startsWith("ngl")) && call.resolvedReceiverInternalName.startsWith("org/lwjgl/opengl/");
}
Expand Down Expand Up @@ -160,13 +178,10 @@ private static String glCall(InterceptedCall call) {

private static boolean isMainThreadMethod(InterceptedCall call) {
if (call.resolvedReceiverInternalName.equals("org/lwjgl/glfw/GLFW")) {
return Arrays
.asList("glfwInit", "glfwTerminate", "glfwCreateWindow", "glfwDefaultWindowHints", "glfwDestroyWindow", "glfwFocusWindow", "glfwGetFramebufferSize", "glfwGetWindowAttrib",
"glfwGetWindowFrameSize", "glfwGetWindowMonitor", "glfwGetWindowPos", "glfwGetWindowSize", "glfwHideWindow", "glfwIconifyWindow", "glfwMaximizeWindow", "glfwPollEvents",
"glfwRestoreWindow", "glfwSetFramebufferSizeCallback", "glfwSetWindowAspectRatio", "glfwSetWindowCloseCallback", "glfwSetWindowFocusCallback", "glfwSetWindowIcon",
"glfwSetWindowIconifyCallback", "glfwSetWindowMonitor", "glfwSetWindowPos", "glfwSetWindowPosCallback", "glfwSetWindowRefreshCallback", "glfwSetWindowSize",
"glfwSetWindowSizeCallback", "glfwSetWindowSizeLimits", "glfwSetWindowTitle", "glfwShowWindow", "glfwWaitEvents", "glfwWaitEventsTimeout", "glfwWindowHint")
.contains(call.name);
return GLFW_MAIN_THREAD_METHODS.contains(call.name);
}
if (call.resolvedReceiverInternalName.startsWith("org/lwjgl/sdl/")) {
return call.name.startsWith("SDL_") && !SDL_THREAD_SAFE_METHODS.contains(call.name);
}
return false;
}
Expand All @@ -178,6 +193,13 @@ private static boolean requiresGlfwInit(InterceptedCall call) {
return false;
}

private static boolean requiresSdlInit(InterceptedCall call) {
if (call.resolvedReceiverInternalName.startsWith("org/lwjgl/sdl/")) {
return call.name.startsWith("SDL_") && !call.name.equals("SDL_Init") && !call.name.equals("SDL_InitSubSystem") && !call.name.equals("SDL_SetMemoryFunctions");
}
return false;
}

private static void checkFunctionSupported(MethodVisitor mv, String name) {
mv.visitFieldInsn(GETFIELD, "org/lwjgl/opengl/GLCapabilities", name, "J");
mv.visitLdcInsn(name);
Expand Down Expand Up @@ -249,6 +271,11 @@ public static Class<?> generate(ClassLoader classLoader, String proxyInternalNam
mv.visitLdcInsn(call.name);
mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "checkGlfwInitialized", "(Ljava/lang/String;)V", false);
}
/* and whether it was an SDL method that requires SDL_Init() to have been called */
if (requiresSdlInit(call)) {
mv.visitLdcInsn(call.name);
mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "checkSdlInitialized", "(Ljava/lang/String;)V", false);
}
}
/* Validate buffer arguments and also load all arguments onto stack */
Type[] paramTypes = Type.getArgumentTypes(call.desc);
Expand Down Expand Up @@ -458,6 +485,12 @@ private static void generateDefaultTraceBefore(InterceptedCall call, MethodVisit
} else if ("GLFWmonitor *".equals(nativeType)) {
mv.visitVarInsn(paramType.getOpcode(ILOAD), var);
mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "paramGlfwMonitor", "(" + MethodCall_Desc + paramType.getDescriptor() + ")" + MethodCall_Desc, false);
} else if ("SDL_Window *".equals(nativeType)) {
mv.visitVarInsn(paramType.getOpcode(ILOAD), var);
mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "paramSdlWindow", "(" + MethodCall_Desc + paramType.getDescriptor() + ")" + MethodCall_Desc, false);
} else if ("SDL_GLContext".equals(nativeType) || "SDL_GLContext *".equals(nativeType)) {
mv.visitVarInsn(paramType.getOpcode(ILOAD), var);
mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "paramSdlGlContext", "(" + MethodCall_Desc + paramType.getDescriptor() + ")" + MethodCall_Desc, false);
} else {
mv.visitVarInsn(paramType.getOpcode(ILOAD), var);
if (paramType.getSort() == Type.ARRAY || paramType.getSort() == Type.OBJECT) {
Expand Down Expand Up @@ -488,6 +521,10 @@ private static void generateDefaultTraceAfter(InterceptedCall call, MethodVisito
mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "returnValueGlfwWindow", "(" + retType.getDescriptor() + MethodCall_Desc + ")" + retType.getDescriptor(), false);
} else if ("GLFWmonitor *".equals(returnNativeType)) {
mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "returnValueGlfwMonitor", "(" + retType.getDescriptor() + MethodCall_Desc + ")" + retType.getDescriptor(), false);
} else if ("SDL_Window *".equals(returnNativeType)) {
mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "returnValueSdlWindow", "(" + retType.getDescriptor() + MethodCall_Desc + ")" + retType.getDescriptor(), false);
} else if ("SDL_GLContext".equals(returnNativeType) || "SDL_GLContext *".equals(returnNativeType)) {
mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "returnValueSdlGlContext", "(" + retType.getDescriptor() + MethodCall_Desc + ")" + retType.getDescriptor(), false);
} else {
mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "returnValue", "(" + retType.getDescriptor() + MethodCall_Desc + ")" + retType.getDescriptor(), false);
}
Expand Down
96 changes: 91 additions & 5 deletions src/org/lwjglx/debug/RT.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ of this software and associated documentation files (the "Software"), to deal
*/
package org.lwjglx.debug;

import static org.lwjglx.debug.Log.error;
import static org.lwjglx.debug.Log.trace;
import static org.lwjglx.debug.org.lwjgl.opengl.Context.*;
import org.lwjgl.PointerBuffer;
import org.lwjglx.debug.org.lwjgl.opengl.Context;

import java.nio.Buffer;
import java.nio.ByteBuffer;
Expand All @@ -37,11 +36,19 @@ of this software and associated documentation files (the "Software"), to deal
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.lwjgl.PointerBuffer;
import org.lwjglx.debug.org.lwjgl.opengl.Context;
import static org.lwjglx.debug.Log.error;
import static org.lwjglx.debug.Log.trace;
import static org.lwjglx.debug.org.lwjgl.opengl.Context.CONTEXTS;
import static org.lwjglx.debug.org.lwjgl.opengl.Context.CURRENT_CONTEXT;
import static org.lwjglx.debug.org.lwjgl.opengl.Context.TextureLayer;
import static org.lwjglx.debug.org.lwjgl.opengl.Context.TextureLevel;
import static org.lwjglx.debug.org.lwjgl.opengl.Context.TextureObject;
import static org.lwjglx.debug.org.lwjgl.opengl.Context.checkFramebufferCompleteness;

class Command {
final List<Param> params;
Expand Down Expand Up @@ -76,6 +83,7 @@ public class RT {

public static Thread mainThread;
public static boolean glfwInitialized;
public static boolean sdlInitialized;

private static void throwIfNotNativeEndianness(ByteOrder order) {
if (order != null && order != ByteOrder.nativeOrder()) {
Expand Down Expand Up @@ -1260,6 +1268,84 @@ public static void checkGlfwWindow(long share) {
throwIAEOrLogError("Provided 'share' argument is not a valid GLFW window handle: " + share);
}

public static final Map<Long, Integer> SDL_WINDOWS = Collections.synchronizedMap(new HashMap<>());
private static final AtomicInteger SDL_WINDOW_COUNTER = new AtomicInteger(1);

public static long registerSdlWindow(long window) {
if (window != 0L) {
SDL_WINDOWS.put(window, SDL_WINDOW_COUNTER.getAndIncrement());
}
return window;
}

public static void destroySdlWindow(long window) {
if (window != 0L) {
SDL_WINDOWS.remove(window);
}
}

public static MethodCall paramSdlWindow(MethodCall mc, long window) {
Integer counter = SDL_WINDOWS.get(window);
if (counter == null) {
mc.param(window);
} else {
mc.paramEnum("window[" + counter + "]");
}
return mc;
}

public static long returnValueSdlWindow(long window, MethodCall mc) {
Integer counter = SDL_WINDOWS.get(window);
if (counter == null) {
mc.returnValue(window);
} else {
mc.returnValueEnum("window[" + counter + "]");
}
return window;
}

public static MethodCall paramSdlGlContext(MethodCall mc, long context) {
Context ctx = CONTEXTS.get(context);
if (ctx == null) {
mc.param(context);
} else {
mc.paramEnum("context[" + ctx.counter + "]");
}
return mc;
}

public static long returnValueSdlGlContext(long context, MethodCall mc) {
Context ctx = CONTEXTS.get(context);
if (ctx == null) {
mc.returnValue(context);
} else {
mc.returnValueEnum("context[" + ctx.counter + "]");
}
return context;
}

public static void checkSdlInitialized(String methodName) {
if (!sdlInitialized) {
throwISEOrLogError("Method " + methodName + " was called before initializing SDL via SDL_Init().");
}
}

public static void checkSdlWindow(long window) {
if (window == 0L)
return;
if (!SDL_WINDOWS.containsKey(window)) {
throwIAEOrLogError("Provided 'window' argument is not a valid SDL window handle: " + window);
}
}

public static void checkSdlGlContext(long context) {
if (context == 0L)
return;
if (!CONTEXTS.containsKey(context)) {
throwIAEOrLogError("Provided 'context' argument is not a valid SDL GL context handle: " + context);
}
}

public static void checkMainMethod(String[] args) {
if (args == null)
return;
Expand Down
8 changes: 5 additions & 3 deletions src/org/lwjglx/debug/org/lwjgl/opengl/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ of this software and associated documentation files (the "Software"), to deal
*/
package org.lwjglx.debug.org.lwjgl.opengl;

import static org.lwjglx.debug.Log.*;
import org.lwjglx.debug.Properties;
import org.lwjglx.debug.RT;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
Expand All @@ -36,7 +37,8 @@ of this software and associated documentation files (the "Software"), to deal
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicInteger;

import org.lwjglx.debug.*;
import static org.lwjglx.debug.Log.LineBreakingStringBuilder;
import static org.lwjglx.debug.Log.info;

public class Context implements Comparable<Context> {
public static class ShareGroup {
Expand Down Expand Up @@ -151,7 +153,7 @@ public static class TimedCodeSection {
public static Context currentContext() {
Context ctx = CURRENT_CONTEXT.get();
if (ctx == null) {
RT.throwISEOrLogError("No OpenGL context has been made current through recognized API methods (glfwMakeContextCurrent).");
RT.throwISEOrLogError("No OpenGL context has been made current through recognized API methods (glfwMakeContextCurrent or SDL_GL_MakeCurrent).");
}
return ctx;
}
Expand Down
Loading