diff --git a/pom.xml b/pom.xml index 791b881..b0d1f98 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.lwjglx lwjglx-debug - 1.0.4 + 1.0.5 LWJGLX/debug Debug LWJGL3 OpenGL applications 2017 diff --git a/src/org/lwjglx/debug/InterceptClassGenerator.java b/src/org/lwjglx/debug/InterceptClassGenerator.java index d0aed6a..95cf4cb 100644 --- a/src/org/lwjglx/debug/InterceptClassGenerator.java +++ b/src/org/lwjglx/debug/InterceptClassGenerator.java @@ -39,7 +39,10 @@ of this software and associated documentation files (the "Software"), to deal import java.util.Collections; import java.util.HashSet; import java.util.Map; +import java.util.NavigableSet; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -148,6 +151,23 @@ class InterceptClassGenerator implements Opcodes { "SDL_GL_MakeCurrent", "SDL_GL_GetCurrentContext", "SDL_GL_SwapWindow", "SDL_GL_DestroyContext" ))); + private static final Set SDL_PRE_INIT_METHODS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + "SDL_Init", "SDL_InitSubSystem", "SDL_SetMemoryFunctions", "SDL_GetMemoryFunctions", + "SDL_malloc", "SDL_calloc", "SDL_realloc", "SDL_free", "SDL_GetVersion", "SDL_GetRevision", + "SDL_GetPlatform", "SDL_GetError", "SDL_ClearError", "SDL_SetError", "SDL_SetMainReady", + "SDL_RunApp" + ))); + + private static final NavigableSet SDL_PRE_INIT_PREFIXES = + Collections.unmodifiableNavigableSet(new TreeSet<>(Arrays.asList( + "SDL_Hint", "SDL_SetHint", "SDL_GetHint", "SDL_ResetHint", "SDL_AddHint", "SDL_RemoveHint", + "SDL_Environment", "SDL_SetEnvironment", "SDL_GetEnvironment", "SDL_UnsetEnvironment", + "SDL_CreateEnvironment", "SDL_DestroyEnvironment", "SDL_Log", "SDL_CreateProperties", + "SDL_DestroyProperties", "SDL_SetProperty", "SDL_GetProperty", "SDL_SetStringProperty", + "SDL_GetStringProperty", "SDL_SetNumberProperty", "SDL_GetNumberProperty", "SDL_SetFloatProperty", + "SDL_GetFloatProperty", "SDL_SetPointerProperty", "SDL_GetPointerProperty" + ))); + private static boolean isGLcall(InterceptedCall call) { return (call.name.startsWith("gl") || call.name.startsWith("ngl")) && call.resolvedReceiverInternalName.startsWith("org/lwjgl/opengl/"); } @@ -195,11 +215,70 @@ private static boolean requiresGlfwInit(InterceptedCall call) { 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"); + switch (call.resolvedReceiverInternalName) { + case "org/lwjgl/sdl/SDLStdinc": + case "org/lwjgl/sdl/SDLHints": + case "org/lwjgl/sdl/SDLError": + case "org/lwjgl/sdl/SDLLog": + case "org/lwjgl/sdl/SDLProperties": + case "org/lwjgl/sdl/SDLVersion": + case "org/lwjgl/sdl/SDLInit": + return false; + } + if (!call.name.startsWith("SDL_")) { + return false; + } + if (SDL_PRE_INIT_METHODS.contains(call.name)) { + return false; + } + + String prefix = SDL_PRE_INIT_PREFIXES.floor(call.name); + return null == prefix || !call.name.startsWith(prefix); } return false; } + private static int getRequiredSdlSubsystem(String receiver) { + switch (receiver) { + case "org/lwjgl/sdl/SDLVideo": + case "org/lwjgl/sdl/SDLRenderer": + case "org/lwjgl/sdl/SDLMessagebox": + case "org/lwjgl/sdl/SDLMouse": + case "org/lwjgl/sdl/SDLKeyboard": + case "org/lwjgl/sdl/SDLTouch": + case "org/lwjgl/sdl/SDLPen": + case "org/lwjgl/sdl/SDLGPU": + case "org/lwjgl/sdl/SDLMetal": + case "org/lwjgl/sdl/SDLVulkan": + case "org/lwjgl/sdl/SDLSystray": + return 0x00000020; // SDL_INIT_VIDEO + + case "org/lwjgl/sdl/SDLAudio": + return 0x00000010; // SDL_INIT_AUDIO + + case "org/lwjgl/sdl/SDLJoystick": + return 0x00000200; // SDL_INIT_JOYSTICK + + case "org/lwjgl/sdl/SDLGamepad": + return 0x00002000; // SDL_INIT_GAMEPAD + + case "org/lwjgl/sdl/SDLEvents": + return 0x00004000; // SDL_INIT_EVENTS + + case "org/lwjgl/sdl/SDLSensor": + return 0x00008000; // SDL_INIT_SENSOR + + case "org/lwjgl/sdl/SDLCamera": + return 0x00010000; // SDL_INIT_CAMERA + + case "org/lwjgl/sdl/SDLHaptic": + return 0x00001000; // SDL_INIT_HAPTIC + + } + return 0; + } + + private static void checkFunctionSupported(MethodVisitor mv, String name) { mv.visitFieldInsn(GETFIELD, "org/lwjgl/opengl/GLCapabilities", name, "J"); mv.visitLdcInsn(name); @@ -274,7 +353,8 @@ public static Class generate(ClassLoader classLoader, String proxyInternalNam /* 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); + mv.visitLdcInsn(getRequiredSdlSubsystem(call.resolvedReceiverInternalName)); + mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "checkSdlInitialized", "(Ljava/lang/String;I)V", false); } } /* Validate buffer arguments and also load all arguments onto stack */ diff --git a/src/org/lwjglx/debug/RT.java b/src/org/lwjglx/debug/RT.java index 605e263..5d31768 100644 --- a/src/org/lwjglx/debug/RT.java +++ b/src/org/lwjglx/debug/RT.java @@ -23,6 +23,7 @@ of this software and associated documentation files (the "Software"), to deal package org.lwjglx.debug; import org.lwjgl.PointerBuffer; +import org.lwjgl.glfw.GLFW; import org.lwjglx.debug.org.lwjgl.opengl.Context; import java.nio.Buffer; @@ -41,6 +42,7 @@ of this software and associated documentation files (the "Software"), to deal import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import static org.lwjgl.sdl.SDLInit.SDL_WasInit; import static org.lwjglx.debug.Log.error; import static org.lwjglx.debug.Log.trace; import static org.lwjglx.debug.org.lwjgl.opengl.Context.CONTEXTS; @@ -703,12 +705,14 @@ public static MethodCall paramGlfwWindow(MethodCall mc, long window) { } public static MethodCall paramGlfwMonitor(MethodCall mc, long monitor) { - PointerBuffer monitors = org.lwjgl.glfw.GLFW.glfwGetMonitors(); - for (int i = 0; i < monitors.remaining(); i++) { - long m = monitors.get(i); - if (m == monitor) { - mc.paramEnum("monitor[" + i + "]"); - return mc; + PointerBuffer monitors = GLFW.glfwGetMonitors(); + if (monitors != null) { + for (int i = 0; i < monitors.remaining(); i++) { + long m = monitors.get(i); + if (m == monitor) { + mc.paramEnum("monitor[" + i + "]"); + return mc; + } } } mc.param(monitor); @@ -758,12 +762,14 @@ public static long returnValueGlfwWindow(long window, MethodCall mc) { } public static long returnValueGlfwMonitor(long monitor, MethodCall mc) { - PointerBuffer monitors = org.lwjgl.glfw.GLFW.glfwGetMonitors(); - for (int i = 0; i < monitors.remaining(); i++) { - long m = monitors.get(i); - if (m == monitor) { - mc.returnValueEnum("monitor[" + i + "]"); - return monitor; + PointerBuffer monitors = GLFW.glfwGetMonitors(); + if (monitors != null) { + for (int i = 0; i < monitors.remaining(); i++) { + long m = monitors.get(i); + if (m == monitor) { + mc.returnValueEnum("monitor[" + i + "]"); + return monitor; + } } } mc.returnValue(monitor); @@ -1250,10 +1256,12 @@ public static void checkGlfwInitialized(String methodName) { public static void checkGlfwMonitor(long monitor) { if (monitor == 0L) return; - PointerBuffer pb = org.lwjgl.glfw.GLFW.glfwGetMonitors(); - for (int i = 0; i < pb.remaining(); i++) { - if (pb.get(i) == monitor) - return; + PointerBuffer pb = GLFW.glfwGetMonitors(); + if (pb != null) { + for (int i = 0; i < pb.remaining(); i++) { + if (pb.get(i) == monitor) + return; + } } throwIAEOrLogError("Provided 'monitor' argument is not a valid GLFW monitor handle: " + monitor); } @@ -1324,9 +1332,24 @@ public static long returnValueSdlGlContext(long context, MethodCall mc) { return context; } - public static void checkSdlInitialized(String methodName) { - if (!sdlInitialized) { - throwISEOrLogError("Method " + methodName + " was called before initializing SDL via SDL_Init()."); + public static void checkSdlInitialized(String methodName, int requiredFlags) { + boolean initialized = false; + try { + if (requiredFlags == 0) { + initialized = sdlInitialized || SDL_WasInit(0) != 0; + } else { + initialized = (SDL_WasInit(requiredFlags) & requiredFlags) == requiredFlags; + } + } catch (Throwable t) { + t.printStackTrace(); + initialized = sdlInitialized; + } + if (!initialized) { + if (requiredFlags == 0) { + throwISEOrLogError("Method " + methodName + " was called before initializing SDL."); + } else { + throwISEOrLogError("Method " + methodName + " was called before initializing the required SDL subsystem."); + } } } diff --git a/src/org/lwjglx/debug/org/lwjgl/sdl/SDLInit.java b/src/org/lwjglx/debug/org/lwjgl/sdl/SDLInit.java index 9c30354..479a1b4 100644 --- a/src/org/lwjglx/debug/org/lwjgl/sdl/SDLInit.java +++ b/src/org/lwjglx/debug/org/lwjgl/sdl/SDLInit.java @@ -29,27 +29,23 @@ public class SDLInit { public static boolean SDL_Init(int flags) { - boolean ret = org.lwjgl.sdl.SDLInit.SDL_Init(flags); - if (ret) { + boolean result = org.lwjgl.sdl.SDLInit.SDL_Init(flags); + if (result) { RT.sdlInitialized = true; - } else { - if (Properties.VALIDATE.enabled) { - error("SDL_Init returned false"); - } + } else if (Properties.VALIDATE.enabled) { + error("SDL_Init returned false"); } - return ret; + return result; } public static boolean SDL_InitSubSystem(int flags) { - boolean ret = org.lwjgl.sdl.SDLInit.SDL_InitSubSystem(flags); - if (ret) { + boolean result = org.lwjgl.sdl.SDLInit.SDL_InitSubSystem(flags); + if (result) { RT.sdlInitialized = true; - } else { - if (Properties.VALIDATE.enabled) { - error("SDL_InitSubSystem returned false"); - } + } else if (Properties.VALIDATE.enabled) { + error("SDL_InitSubSystem returned false"); } - return ret; + return result; } public static void SDL_Quit() { diff --git a/test/test/DebugSDLIT.java b/test/test/DebugSDLIT.java index 4fcd5d8..c406bf5 100644 --- a/test/test/DebugSDLIT.java +++ b/test/test/DebugSDLIT.java @@ -4,9 +4,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; +import org.lwjgl.sdl.SDLError; +import org.lwjgl.sdl.SDLHints; +import org.lwjgl.sdl.SDLInit; +import org.lwjgl.sdl.SDLStdinc; +import org.lwjgl.sdl.SDLTimer; import org.lwjglx.debug.Properties; import org.lwjglx.debug.org.lwjgl.opengl.Context; +import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.regex.Pattern; @@ -172,4 +178,54 @@ public void testGLError() { createCapabilities(); assertThrowsWithMessage(IllegalStateException.class, () -> glEnable(GL_VERTEX_ARRAY_POINTER), Pattern.compile("glEnable produced error: 1280 \\(GL_INVALID_ENUM\\)")); } + + @Test + public void testPreInitAllowed() { + SDL_Quit(); + alreadyTerminated = true; + assertTrue(SDLHints.SDL_SetHint("SDL_VIDEO_DRIVER", "dummy")); + assertEquals("dummy", SDLHints.SDL_GetHint("SDL_VIDEO_DRIVER")); + SDLError.SDL_GetError(); + ByteBuffer buf = SDLStdinc.SDL_malloc(16); + if (buf != null) { + SDLStdinc.SDL_free(buf); + } + assertTrue(SDL_Init(SDL_INIT_VIDEO)); + alreadyTerminated = false; + window = SDL_CreateWindow("Test PreInit Allowed", 320, 240, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); + assertNotNull(window); + } + + @Test + public void testSubsystemRefcounting() { + SDL_Quit(); + alreadyTerminated = true; + assertTrue(SDL_Init(SDL_INIT_VIDEO)); + assertTrue(SDL_Init(SDL_INIT_VIDEO)); + alreadyTerminated = false; + window = SDL_CreateWindow("Test Refcount 1", 320, 240, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); + assertNotNull(window); + SDLInit.SDL_QuitSubSystem(SDL_INIT_VIDEO); + window2 = SDL_CreateWindow("Test Refcount 2", 320, 240, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); + assertNotNull(window2); + SDL_DestroyWindow(window); + window = 0L; + SDL_DestroyWindow(window2); + window2 = 0L; + SDLInit.SDL_QuitSubSystem(SDL_INIT_VIDEO); + alreadyTerminated = true; + assertThrows(IllegalStateException.class, () -> SDL_CreateWindow("Should Fail", 320, 240, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN)); + } + + @Test + public void testInitZero() { + SDL_Quit(); + alreadyTerminated = true; + assertTrue(SDL_Init(0)); + // core/timer method should succeed + long ticks = SDLTimer.SDL_GetTicks(); + assertTrue(ticks >= 0L); + // video method should throw IllegalStateException because video subsystem is not initialized + assertThrows(IllegalStateException.class, () -> SDL_CreateWindow("Should Fail", 320, 240, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN)); + } }