From 3fde834ed796e1a467050d3f708ee8573171ea18 Mon Sep 17 00:00:00 2001 From: Mike Duigou Date: Wed, 30 Sep 2026 13:46:47 -0700 Subject: [PATCH 1/3] add OpenAL validation with resource lifetime tracking --- pom.xml | 15 +- .../lwjglx/debug/InterceptClassGenerator.java | 25 + src/org/lwjglx/debug/openal/ALContext.java | 50 ++ src/org/lwjglx/debug/openal/ALDevice.java | 39 + src/org/lwjglx/debug/openal/ALMetadata.java | 196 +++++ src/org/lwjglx/debug/openal/ALObjects.java | 81 ++ src/org/lwjglx/debug/openal/ALRT.java | 257 ++++++ .../lwjglx/debug/openal/ResourceState.java | 6 + src/org/lwjglx/debug/org/lwjgl/openal/AL.java | 21 + .../lwjglx/debug/org/lwjgl/openal/AL10.java | 777 ++++++++++++++++++ .../lwjglx/debug/org/lwjgl/openal/AL11.java | 184 +++++ .../lwjglx/debug/org/lwjgl/openal/ALC.java | 32 + .../lwjglx/debug/org/lwjgl/openal/ALC10.java | 286 +++++++ .../lwjglx/debug/org/lwjgl/openal/ALC11.java | 96 +++ .../lwjglx/debug/org/lwjgl/openal/EXTEfx.java | 585 +++++++++++++ test/test/DebugALIT.java | 289 +++++++ 16 files changed, 2938 insertions(+), 1 deletion(-) create mode 100644 src/org/lwjglx/debug/openal/ALContext.java create mode 100644 src/org/lwjglx/debug/openal/ALDevice.java create mode 100644 src/org/lwjglx/debug/openal/ALMetadata.java create mode 100644 src/org/lwjglx/debug/openal/ALObjects.java create mode 100644 src/org/lwjglx/debug/openal/ALRT.java create mode 100644 src/org/lwjglx/debug/openal/ResourceState.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/openal/AL.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/openal/AL10.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/openal/AL11.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/openal/ALC.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/openal/ALC10.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/openal/ALC11.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/openal/EXTEfx.java create mode 100644 test/test/DebugALIT.java diff --git a/pom.xml b/pom.xml index 861bc2e..af3c44f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.lwjglx lwjglx-debug - 1.0.6 + 1.1.0 LWJGLX/debug Debug LWJGL3 OpenGL applications 2017 @@ -420,6 +420,12 @@ ${lwjgl.version} provided + + org.lwjgl + lwjgl-openal + ${lwjgl.version} + provided + org.lwjgl lwjgl @@ -448,5 +454,12 @@ natives-${platform} provided + + org.lwjgl + lwjgl-openal + ${lwjgl.version} + natives-${platform} + provided + diff --git a/src/org/lwjglx/debug/InterceptClassGenerator.java b/src/org/lwjglx/debug/InterceptClassGenerator.java index 410a128..b474bd5 100644 --- a/src/org/lwjglx/debug/InterceptClassGenerator.java +++ b/src/org/lwjglx/debug/InterceptClassGenerator.java @@ -172,6 +172,15 @@ private static boolean isGLcall(InterceptedCall call) { return (call.name.startsWith("gl") || call.name.startsWith("ngl")) && call.resolvedReceiverInternalName.startsWith("org/lwjgl/opengl/"); } + private static boolean isALcall(InterceptedCall call) { + String name = call.name; + boolean isAl = (name.startsWith("al") && !name.startsWith("alc")) + || (name.startsWith("nal") && !name.startsWith("nalc")); + return isAl && call.resolvedReceiverInternalName.startsWith("org/lwjgl/openal/") + && !call.resolvedReceiverInternalName.equals("org/lwjgl/openal/AL") + && !name.equals("alGetError"); + } + private static String glCall(InterceptedCall call) { if (!isGLcall(call)) return null; @@ -537,6 +546,11 @@ private static void callUserMethodOrDirect(ClassLoader classLoader, InterceptedC mv.visitLdcInsn(call.name); mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "checkError", "(Ljava/lang/String;)V", false); } + /* Check AL error if it was an AL call */ + if (VALIDATE.enabled && isALcall(call)) { + mv.visitLdcInsn(call.name); + mv.visitMethodInsn(INVOKESTATIC, "org/lwjglx/debug/openal/ALRT", "checkALError", "(Ljava/lang/String;)V", false); + } } private static int loadGLenum(String name, String helperMethod, MethodVisitor mv, int var, int glEnumIndex) { @@ -588,6 +602,15 @@ private static void generateDefaultTraceBefore(InterceptedCall call, MethodVisit } 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 if ("ALenum".equals(nativeType) || "ALCenum".equals(nativeType)) { + mv.visitVarInsn(paramType.getOpcode(ILOAD), var); + mv.visitMethodInsn(INVOKESTATIC, "org/lwjglx/debug/openal/ALRT", "paramAlEnum", "(" + MethodCall_Desc + "I)" + MethodCall_Desc, false); + } else if ("ALCdevice *".equals(nativeType)) { + mv.visitVarInsn(paramType.getOpcode(ILOAD), var); + mv.visitMethodInsn(INVOKESTATIC, "org/lwjglx/debug/openal/ALRT", "paramAlDevice", "(" + MethodCall_Desc + "J)" + MethodCall_Desc, false); + } else if ("ALCcontext *".equals(nativeType)) { + mv.visitVarInsn(paramType.getOpcode(ILOAD), var); + mv.visitMethodInsn(INVOKESTATIC, "org/lwjglx/debug/openal/ALRT", "paramAlContext", "(" + MethodCall_Desc + "J)" + MethodCall_Desc, false); } else { mv.visitVarInsn(paramType.getOpcode(ILOAD), var); if (paramType.getSort() == Type.ARRAY || paramType.getSort() == Type.OBJECT) { @@ -614,6 +637,8 @@ private static void generateDefaultTraceAfter(InterceptedCall call, MethodVisito String returnNativeType = minfo.returnNativeType; if ("GLenum".equals(returnNativeType) || "GLboolean".equals(returnNativeType)) { loadGLenumReturn(call.glName, "glEnumReturn", mv); + } else if ("ALenum".equals(returnNativeType) || "ALCenum".equals(returnNativeType)) { + mv.visitMethodInsn(INVOKESTATIC, "org/lwjglx/debug/openal/ALRT", "returnValueAlEnum", "(I" + MethodCall_Desc + ")I", false); } else if ("GLFWwindow *".equals(returnNativeType)) { mv.visitMethodInsn(INVOKESTATIC, RT_InternalName, "returnValueGlfwWindow", "(" + retType.getDescriptor() + MethodCall_Desc + ")" + retType.getDescriptor(), false); } else if ("GLFWmonitor *".equals(returnNativeType)) { diff --git a/src/org/lwjglx/debug/openal/ALContext.java b/src/org/lwjglx/debug/openal/ALContext.java new file mode 100644 index 0000000..46ee8cf --- /dev/null +++ b/src/org/lwjglx/debug/openal/ALContext.java @@ -0,0 +1,50 @@ +package org.lwjglx.debug.openal; + +import java.util.concurrent.ConcurrentHashMap; + +import org.lwjglx.debug.Properties; + +public class ALContext { + + private static final ConcurrentHashMap CONTEXTS = new ConcurrentHashMap<>(); + private static volatile ALContext currentContext; + + public final long handle; + public final ALDevice device; + public volatile ResourceState state = ResourceState.ALIVE; + public final ConcurrentHashMap sources = new ConcurrentHashMap<>(); + public final ConcurrentHashMap buffers = new ConcurrentHashMap<>(); + public final ConcurrentHashMap effects = new ConcurrentHashMap<>(); + public final ConcurrentHashMap filters = new ConcurrentHashMap<>(); + public final ConcurrentHashMap auxSlots = new ConcurrentHashMap<>(); + public final Throwable creationSite; + + private ALContext(long handle, ALDevice device) { + this.handle = handle; + this.device = device; + this.creationSite = Properties.STRICT.enabled ? new Throwable("Context created here") : null; + } + + public static ALContext create(long handle, ALDevice device) { + ALContext context = new ALContext(handle, device); + CONTEXTS.put(handle, context); + device.contexts.add(context); + return context; + } + + public static ALContext get(long handle) { + return CONTEXTS.get(handle); + } + + public static java.util.Collection allContexts() { + return CONTEXTS.values(); + } + + public static ALContext currentContext() { + return currentContext; + } + + public static void makeCurrent(ALContext context) { + currentContext = context; + } +} diff --git a/src/org/lwjglx/debug/openal/ALDevice.java b/src/org/lwjglx/debug/openal/ALDevice.java new file mode 100644 index 0000000..1fde04c --- /dev/null +++ b/src/org/lwjglx/debug/openal/ALDevice.java @@ -0,0 +1,39 @@ +package org.lwjglx.debug.openal; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.lwjglx.debug.Properties; + +public class ALDevice { + + private static final ConcurrentHashMap DEVICES = new ConcurrentHashMap<>(); + + public final long handle; + public volatile ResourceState state = ResourceState.ALIVE; + public final Set contexts = ConcurrentHashMap.newKeySet(); + public final Throwable creationSite; + + private ALDevice(long handle) { + this.handle = handle; + this.creationSite = Properties.STRICT.enabled ? new Throwable("Device opened here") : null; + } + + public static ALDevice create(long handle) { + ALDevice device = new ALDevice(handle); + DEVICES.put(handle, device); + return device; + } + + public static ALDevice get(long handle) { + return DEVICES.get(handle); + } + + public static java.util.Collection allDevices() { + return DEVICES.values(); + } + + public static ALDevice remove(long handle) { + return DEVICES.remove(handle); + } +} diff --git a/src/org/lwjglx/debug/openal/ALMetadata.java b/src/org/lwjglx/debug/openal/ALMetadata.java new file mode 100644 index 0000000..7260b02 --- /dev/null +++ b/src/org/lwjglx/debug/openal/ALMetadata.java @@ -0,0 +1,196 @@ +package org.lwjglx.debug.openal; + +import java.util.HashMap; +import java.util.Map; + +/** + * Maps OpenAL enum values and error codes to human-readable strings. + * Hand-written since the OpenAL enum space is small enough to not need a generator. + */ +public class ALMetadata { + + private static final Map AL_ENUMS = new HashMap<>(); + private static final Map AL_ERRORS = new HashMap<>(); + private static final Map ALC_ERRORS = new HashMap<>(); + + static { + /* AL errors */ + AL_ERRORS.put(0x0000, "AL_NO_ERROR"); + AL_ERRORS.put(0xA001, "AL_INVALID_NAME"); + AL_ERRORS.put(0xA002, "AL_INVALID_ENUM"); + AL_ERRORS.put(0xA003, "AL_INVALID_VALUE"); + AL_ERRORS.put(0xA004, "AL_INVALID_OPERATION"); + AL_ERRORS.put(0xA005, "AL_OUT_OF_MEMORY"); + + /* ALC errors */ + ALC_ERRORS.put(0x0000, "ALC_NO_ERROR"); + ALC_ERRORS.put(0xA001, "ALC_INVALID_DEVICE"); + ALC_ERRORS.put(0xA002, "ALC_INVALID_CONTEXT"); + ALC_ERRORS.put(0xA003, "ALC_INVALID_ENUM"); + ALC_ERRORS.put(0xA004, "ALC_INVALID_VALUE"); + ALC_ERRORS.put(0xA006, "ALC_OUT_OF_MEMORY"); + + /* AL error codes (also enums) */ + e(0x0000, "AL_NO_ERROR"); + e(0xA001, "AL_INVALID_NAME"); + e(0xA002, "AL_INVALID_ENUM"); + e(0xA003, "AL_INVALID_VALUE"); + e(0xA004, "AL_INVALID_OPERATION"); + e(0xA005, "AL_OUT_OF_MEMORY"); + + /* AL boolean / state */ + e(0x0001, "AL_TRUE"); + + /* Source properties */ + e(0x1001, "AL_SOURCE_RELATIVE"); + e(0x1002, "AL_CONE_INNER_ANGLE"); + e(0x1003, "AL_CONE_OUTER_ANGLE"); + e(0x1004, "AL_PITCH"); + e(0x1005, "AL_POSITION"); + e(0x1006, "AL_DIRECTION"); + e(0x1007, "AL_VELOCITY"); + e(0x1008, "AL_LOOPING"); + e(0x1009, "AL_BUFFER"); + e(0x100A, "AL_GAIN"); + e(0x100B, "AL_MIN_GAIN"); + e(0x100C, "AL_MAX_GAIN"); + e(0x100D, "AL_ORIENTATION"); + + /* Source state */ + e(0x1010, "AL_SOURCE_STATE"); + e(0x1011, "AL_INITIAL"); + e(0x1012, "AL_PLAYING"); + e(0x1013, "AL_PAUSED"); + e(0x1014, "AL_STOPPED"); + e(0x1015, "AL_BUFFERS_QUEUED"); + e(0x1016, "AL_BUFFERS_PROCESSED"); + + /* Source type */ + e(0x1027, "AL_SOURCE_TYPE"); + e(0x1028, "AL_STATIC"); + e(0x1029, "AL_STREAMING"); + e(0x1030, "AL_UNDETERMINED"); + + /* Source distance */ + e(0x1020, "AL_REFERENCE_DISTANCE"); + e(0x1021, "AL_ROLLOFF_FACTOR"); + e(0x1022, "AL_CONE_OUTER_GAIN"); + e(0x1023, "AL_MAX_DISTANCE"); + e(0x1024, "AL_SEC_OFFSET"); + e(0x1025, "AL_SAMPLE_OFFSET"); + e(0x1026, "AL_BYTE_OFFSET"); + + /* Buffer format */ + e(0x1100, "AL_FORMAT_MONO8"); + e(0x1101, "AL_FORMAT_MONO16"); + e(0x1102, "AL_FORMAT_STEREO8"); + e(0x1103, "AL_FORMAT_STEREO16"); + + /* Buffer properties */ + e(0x2001, "AL_FREQUENCY"); + e(0x2002, "AL_BITS"); + e(0x2003, "AL_CHANNELS"); + e(0x2004, "AL_SIZE"); + + /* Listener */ + e(0x100F, "AL_ORIENTATION"); + + /* Global state */ + e(0xB000, "AL_DOPPLER_FACTOR"); + e(0xB001, "AL_DOPPLER_VELOCITY"); + e(0xB003, "AL_SPEED_OF_SOUND"); + e(0xD000, "AL_DISTANCE_MODEL"); + e(0xD001, "AL_INVERSE_DISTANCE"); + e(0xD002, "AL_INVERSE_DISTANCE_CLAMPED"); + e(0xD003, "AL_LINEAR_DISTANCE"); + e(0xD004, "AL_LINEAR_DISTANCE_CLAMPED"); + e(0xD005, "AL_EXPONENT_DISTANCE"); + e(0xD006, "AL_EXPONENT_DISTANCE_CLAMPED"); + + /* AL string queries */ + e(0xB001, "AL_VENDOR"); + e(0xB002, "AL_VERSION"); + e(0xB003, "AL_RENDERER"); + e(0xB004, "AL_EXTENSIONS"); + + /* ALC properties */ + e(0x1007, "ALC_FREQUENCY"); + e(0x1008, "ALC_REFRESH"); + e(0x1009, "ALC_SYNC"); + e(0x1010, "ALC_MONO_SOURCES"); + e(0x1011, "ALC_STEREO_SOURCES"); + + /* ALC string queries */ + e(0x0004, "ALC_DEFAULT_DEVICE_SPECIFIER"); + e(0x0005, "ALC_DEVICE_SPECIFIER"); + e(0x0006, "ALC_EXTENSIONS"); + e(0x0310, "ALC_MAJOR_VERSION"); + e(0x0311, "ALC_MINOR_VERSION"); + e(0x0312, "ALC_ATTRIBUTES_SIZE"); + e(0x0313, "ALC_ALL_ATTRIBUTES"); + + /* ALC 1.1 */ + e(0x0311, "ALC_CAPTURE_DEVICE_SPECIFIER"); + e(0x0312, "ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER"); + e(0x0311, "ALC_CAPTURE_SAMPLES"); + + /* EXTEfx - Effect types */ + e(0x0001, "AL_EFFECT_REVERB"); + e(0x0002, "AL_EFFECT_CHORUS"); + e(0x0003, "AL_EFFECT_DISTORTION"); + e(0x0004, "AL_EFFECT_ECHO"); + e(0x0005, "AL_EFFECT_FLANGER"); + e(0x0006, "AL_EFFECT_FREQUENCY_SHIFTER"); + e(0x0007, "AL_EFFECT_VOCAL_MORPHER"); + e(0x0008, "AL_EFFECT_PITCH_SHIFTER"); + e(0x0009, "AL_EFFECT_RING_MODULATOR"); + e(0x000A, "AL_EFFECT_AUTOWAH"); + e(0x000B, "AL_EFFECT_COMPRESSOR"); + e(0x000C, "AL_EFFECT_EQUALIZER"); + e(0x8000, "AL_EFFECT_EAXREVERB"); + + /* EXTEfx - Filter types */ + e(0x0001, "AL_FILTER_LOWPASS"); + e(0x0002, "AL_FILTER_HIGHPASS"); + e(0x0003, "AL_FILTER_BANDPASS"); + + /* EXTEfx - Source properties */ + e(0x20000, "AL_DIRECT_FILTER"); + e(0x20001, "AL_AUXILIARY_SEND_FILTER"); + e(0x20002, "AL_AIR_ABSORPTION_FACTOR"); + e(0x20003, "AL_ROOM_ROLLOFF_FACTOR"); + e(0x20004, "AL_CONE_OUTER_GAINHF"); + e(0x20005, "AL_DIRECT_FILTER_GAINHF_AUTO"); + e(0x20006, "AL_AUXILIARY_SEND_FILTER_GAIN_AUTO"); + e(0x20007, "AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO"); + + /* EXTEfx - Effect slot */ + e(0x0001, "AL_EFFECTSLOT_EFFECT"); + e(0x0002, "AL_EFFECTSLOT_GAIN"); + e(0x0003, "AL_EFFECTSLOT_AUXILIARY_SEND_AUTO"); + e(0x0004, "AL_EFFECTSLOT_NULL"); + + /* EXTEfx - Effect/Filter properties */ + e(0x8001, "AL_EFFECT_TYPE"); + e(0x8002, "AL_FILTER_TYPE"); + } + + private static void e(int value, String name) { + AL_ENUMS.putIfAbsent(value, name); + } + + public static String alErrorName(int error) { + String name = AL_ERRORS.get(error); + return name != null ? name : "Unknown AL error 0x" + Integer.toHexString(error); + } + + public static String alcErrorName(int error) { + String name = ALC_ERRORS.get(error); + return name != null ? name : "Unknown ALC error 0x" + Integer.toHexString(error); + } + + public static String enumName(int value) { + String name = AL_ENUMS.get(value); + return name != null ? name : "0x" + Integer.toHexString(value); + } +} diff --git a/src/org/lwjglx/debug/openal/ALObjects.java b/src/org/lwjglx/debug/openal/ALObjects.java new file mode 100644 index 0000000..c608a5f --- /dev/null +++ b/src/org/lwjglx/debug/openal/ALObjects.java @@ -0,0 +1,81 @@ +package org.lwjglx.debug.openal; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.lwjglx.debug.Properties; + +public class ALObjects { + + public static class Buffer { + public final int name; + public final ALContext owningContext; + public volatile ResourceState state = ResourceState.ALIVE; + public final Set attachedSources = ConcurrentHashMap.newKeySet(); + public final Throwable creationSite; + public volatile Throwable deletionSite; + + public Buffer(int name, ALContext owningContext) { + this.name = name; + this.owningContext = owningContext; + this.creationSite = Properties.STRICT.enabled ? new Throwable("Buffer created here") : null; + } + } + + public static class Source { + public final int name; + public final ALContext owningContext; + public volatile ResourceState state = ResourceState.ALIVE; + public volatile Buffer attachedBuffer; + public final Throwable creationSite; + public volatile Throwable deletionSite; + + public Source(int name, ALContext owningContext) { + this.name = name; + this.owningContext = owningContext; + this.creationSite = Properties.STRICT.enabled ? new Throwable("Source created here") : null; + } + } + + public static class Effect { + public final int name; + public final ALContext owningContext; + public volatile ResourceState state = ResourceState.ALIVE; + public final Throwable creationSite; + public volatile Throwable deletionSite; + + public Effect(int name, ALContext owningContext) { + this.name = name; + this.owningContext = owningContext; + this.creationSite = Properties.STRICT.enabled ? new Throwable("Effect created here") : null; + } + } + + public static class Filter { + public final int name; + public final ALContext owningContext; + public volatile ResourceState state = ResourceState.ALIVE; + public final Throwable creationSite; + public volatile Throwable deletionSite; + + public Filter(int name, ALContext owningContext) { + this.name = name; + this.owningContext = owningContext; + this.creationSite = Properties.STRICT.enabled ? new Throwable("Filter created here") : null; + } + } + + public static class AuxiliaryEffectSlot { + public final int name; + public final ALContext owningContext; + public volatile ResourceState state = ResourceState.ALIVE; + public final Throwable creationSite; + public volatile Throwable deletionSite; + + public AuxiliaryEffectSlot(int name, ALContext owningContext) { + this.name = name; + this.owningContext = owningContext; + this.creationSite = Properties.STRICT.enabled ? new Throwable("Auxiliary effect slot created here") : null; + } + } +} diff --git a/src/org/lwjglx/debug/openal/ALRT.java b/src/org/lwjglx/debug/openal/ALRT.java new file mode 100644 index 0000000..4560b00 --- /dev/null +++ b/src/org/lwjglx/debug/openal/ALRT.java @@ -0,0 +1,257 @@ +package org.lwjglx.debug.openal; + +import static org.lwjglx.debug.Log.*; + +import org.lwjglx.debug.Properties; +import org.lwjglx.debug.RT; + +/** + * OpenAL runtime validation helpers. + */ +public class ALRT { + + public static ALContext checkContext(String methodName) { + ALContext ctx = ALContext.currentContext(); + if (ctx == null) { + RT.throwISEOrLogError("No OpenAL context is current when calling " + methodName); + } else if (ctx.state == ResourceState.DELETED) { + RT.throwISEOrLogError("Current OpenAL context has been destroyed when calling " + methodName); + } + return ctx; + } + + public static void checkDevice(ALDevice device, long handle, String methodName) { + if (device == null) { + RT.throwISEOrLogError(methodName + ": unknown device handle 0x" + Long.toHexString(handle)); + } else if (device.state == ResourceState.DELETED) { + RT.throwISEOrLogError(methodName + ": device has been closed" + deletionInfo(device.creationSite)); + } + } + + public static ALObjects.Source checkSource(ALContext ctx, int source, String methodName) { + if (ctx == null) return null; + ALObjects.Source s = ctx.sources.get(source); + if (s == null) { + ALObjects.Source other = findSourceInAnyContext(source); + if (other != null) { + RT.throwISEOrLogError(methodName + ": source " + source + " belongs to context 0x" + + Long.toHexString(other.owningContext.handle) + ", not the current context 0x" + + Long.toHexString(ctx.handle) + " (cross-context use)"); + return null; + } + RT.throwISEOrLogError(methodName + ": unknown source name " + source); + return null; + } + if (s.state == ResourceState.DELETED) { + RT.throwISEOrLogError(methodName + ": source " + source + " has been deleted (use-after-free)" + + deletionInfo(s.deletionSite) + creationInfo(s.creationSite)); + return null; + } + return s; + } + + public static ALObjects.Buffer checkBuffer(ALContext ctx, int buffer, String methodName) { + if (ctx == null || buffer == 0) return null; + ALObjects.Buffer b = ctx.buffers.get(buffer); + if (b == null) { + ALObjects.Buffer other = findBufferInAnyContext(buffer); + if (other != null) { + RT.throwISEOrLogError(methodName + ": buffer " + buffer + " belongs to context 0x" + + Long.toHexString(other.owningContext.handle) + ", not the current context 0x" + + Long.toHexString(ctx.handle) + " (cross-context use)"); + return null; + } + RT.throwISEOrLogError(methodName + ": unknown buffer name " + buffer); + return null; + } + if (b.state == ResourceState.DELETED) { + RT.throwISEOrLogError(methodName + ": buffer " + buffer + " has been deleted (use-after-free)" + + deletionInfo(b.deletionSite) + creationInfo(b.creationSite)); + return null; + } + return b; + } + + public static ALObjects.Effect checkEffect(ALContext ctx, int effect, String methodName) { + if (ctx == null || effect == 0) return null; + ALObjects.Effect e = ctx.effects.get(effect); + if (e == null) { + ALObjects.Effect other = findEffectInAnyContext(effect); + if (other != null) { + RT.throwISEOrLogError(methodName + ": effect " + effect + " belongs to context 0x" + + Long.toHexString(other.owningContext.handle) + ", not the current context 0x" + + Long.toHexString(ctx.handle) + " (cross-context use)"); + return null; + } + RT.throwISEOrLogError(methodName + ": unknown effect name " + effect); + return null; + } + if (e.state == ResourceState.DELETED) { + RT.throwISEOrLogError(methodName + ": effect " + effect + " has been deleted (use-after-free)" + + deletionInfo(e.deletionSite) + creationInfo(e.creationSite)); + return null; + } + return e; + } + + public static ALObjects.Filter checkFilter(ALContext ctx, int filter, String methodName) { + if (ctx == null || filter == 0) return null; + ALObjects.Filter f = ctx.filters.get(filter); + if (f == null) { + ALObjects.Filter other = findFilterInAnyContext(filter); + if (other != null) { + RT.throwISEOrLogError(methodName + ": filter " + filter + " belongs to context 0x" + + Long.toHexString(other.owningContext.handle) + ", not the current context 0x" + + Long.toHexString(ctx.handle) + " (cross-context use)"); + return null; + } + RT.throwISEOrLogError(methodName + ": unknown filter name " + filter); + return null; + } + if (f.state == ResourceState.DELETED) { + RT.throwISEOrLogError(methodName + ": filter " + filter + " has been deleted (use-after-free)" + + deletionInfo(f.deletionSite) + creationInfo(f.creationSite)); + return null; + } + return f; + } + + public static ALObjects.AuxiliaryEffectSlot checkAuxSlot(ALContext ctx, int slot, String methodName) { + if (ctx == null || slot == 0) return null; + ALObjects.AuxiliaryEffectSlot a = ctx.auxSlots.get(slot); + if (a == null) { + ALObjects.AuxiliaryEffectSlot other = findAuxSlotInAnyContext(slot); + if (other != null) { + RT.throwISEOrLogError(methodName + ": auxiliary effect slot " + slot + " belongs to context 0x" + + Long.toHexString(other.owningContext.handle) + ", not the current context 0x" + + Long.toHexString(ctx.handle) + " (cross-context use)"); + return null; + } + RT.throwISEOrLogError(methodName + ": unknown auxiliary effect slot name " + slot); + return null; + } + if (a.state == ResourceState.DELETED) { + RT.throwISEOrLogError(methodName + ": auxiliary effect slot " + slot + " has been deleted (use-after-free)" + + deletionInfo(a.deletionSite) + creationInfo(a.creationSite)); + return null; + } + return a; + } + + private static ALObjects.Source findSourceInAnyContext(int source) { + for (ALContext c : ALContext.allContexts()) { + ALObjects.Source s = c.sources.get(source); + if (s != null) return s; + } + return null; + } + + private static ALObjects.Buffer findBufferInAnyContext(int buffer) { + for (ALContext c : ALContext.allContexts()) { + ALObjects.Buffer b = c.buffers.get(buffer); + if (b != null) return b; + } + return null; + } + + private static ALObjects.Effect findEffectInAnyContext(int effect) { + for (ALContext c : ALContext.allContexts()) { + ALObjects.Effect e = c.effects.get(effect); + if (e != null) return e; + } + return null; + } + + private static ALObjects.Filter findFilterInAnyContext(int filter) { + for (ALContext c : ALContext.allContexts()) { + ALObjects.Filter f = c.filters.get(filter); + if (f != null) return f; + } + return null; + } + + private static ALObjects.AuxiliaryEffectSlot findAuxSlotInAnyContext(int slot) { + for (ALContext c : ALContext.allContexts()) { + ALObjects.AuxiliaryEffectSlot a = c.auxSlots.get(slot); + if (a != null) return a; + } + return null; + } + + public static void checkALError(String methodName) { + ALContext ctx = ALContext.currentContext(); + if (ctx == null || ctx.state == ResourceState.DELETED) { + return; + } + try { + if (org.lwjgl.openal.AL.getCapabilities() == null) { + return; + } + } catch (Throwable t) { + return; + } + int err = org.lwjgl.openal.AL10.alGetError(); + if (err != 0) { + RT.throwISEOrLogError(methodName + " produced error: " + ALMetadata.alErrorName(err)); + } + } + + public static void checkALCError(long device, String methodName) { + int err = org.lwjgl.openal.ALC10.alcGetError(device); + if (err != 0) { + RT.throwISEOrLogError(methodName + " produced error: " + ALMetadata.alcErrorName(err)); + } + } + + public static String deletionInfo(Throwable deletionSite) { + if (deletionSite != null) { + return "\n Deleted at: " + formatTrace(deletionSite); + } + return ""; + } + + public static String creationInfo(Throwable creationSite) { + if (creationSite != null) { + return "\n Created at: " + formatTrace(creationSite); + } + return ""; + } + + private static String formatTrace(Throwable t) { + StackTraceElement[] stack = t.getStackTrace(); + for (StackTraceElement ste : stack) { + if (!ste.getClassName().startsWith("org.lwjglx.debug")) { + return ste.toString(); + } + } + return stack.length > 0 ? stack[0].toString() : "(unknown)"; + } + + public static org.lwjglx.debug.MethodCall paramAlEnum(org.lwjglx.debug.MethodCall mc, int val) { + mc.paramEnum(ALMetadata.enumName(val)); + return mc; + } + + public static org.lwjglx.debug.MethodCall paramAlDevice(org.lwjglx.debug.MethodCall mc, long device) { + if (device == 0L) { + mc.paramEnum("device[NULL]"); + } else { + mc.paramEnum("device[0x" + Long.toHexString(device) + "]"); + } + return mc; + } + + public static org.lwjglx.debug.MethodCall paramAlContext(org.lwjglx.debug.MethodCall mc, long context) { + if (context == 0L) { + mc.paramEnum("context[NULL]"); + } else { + mc.paramEnum("context[0x" + Long.toHexString(context) + "]"); + } + return mc; + } + + public static int returnValueAlEnum(int val, org.lwjglx.debug.MethodCall mc) { + mc.returnValueEnum(ALMetadata.enumName(val)); + return val; + } +} diff --git a/src/org/lwjglx/debug/openal/ResourceState.java b/src/org/lwjglx/debug/openal/ResourceState.java new file mode 100644 index 0000000..b5d3dca --- /dev/null +++ b/src/org/lwjglx/debug/openal/ResourceState.java @@ -0,0 +1,6 @@ +package org.lwjglx.debug.openal; + +public enum ResourceState { + ALIVE, + DELETED +} diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/AL.java b/src/org/lwjglx/debug/org/lwjgl/openal/AL.java new file mode 100644 index 0000000..ef91cc2 --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/openal/AL.java @@ -0,0 +1,21 @@ +package org.lwjglx.debug.org.lwjgl.openal; + +import java.util.function.IntFunction; + +import org.lwjgl.PointerBuffer; +import org.lwjgl.openal.ALCCapabilities; +import org.lwjgl.openal.ALCapabilities; +import org.lwjglx.debug.openal.ALRT; + +public class AL { + + public static ALCapabilities createCapabilities(ALCCapabilities alcCaps) { + ALRT.checkContext("AL.createCapabilities"); + return org.lwjgl.openal.AL.createCapabilities(alcCaps); + } + + public static ALCapabilities createCapabilities(ALCCapabilities alcCaps, IntFunction bufferFactory) { + ALRT.checkContext("AL.createCapabilities"); + return org.lwjgl.openal.AL.createCapabilities(alcCaps, bufferFactory); + } +} diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java b/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java new file mode 100644 index 0000000..df485a5 --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java @@ -0,0 +1,777 @@ +package org.lwjglx.debug.org.lwjgl.openal; + +import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import org.lwjglx.debug.Properties; +import org.lwjglx.debug.RT; +import org.lwjglx.debug.openal.ALContext; +import org.lwjglx.debug.openal.ALObjects; +import org.lwjglx.debug.openal.ALRT; +import org.lwjglx.debug.openal.ResourceState; + +public class AL10 { + + /* --- Buffer Management --- */ + + public static int alGenBuffers() { + ALContext ctx = ALRT.checkContext("alGenBuffers"); + int buffer = org.lwjgl.openal.AL10.alGenBuffers(); + if (ctx != null && buffer != 0) { + ctx.buffers.put(buffer, new ALObjects.Buffer(buffer, ctx)); + } + return buffer; + } + + public static void alGenBuffers(IntBuffer buffers) { + ALContext ctx = ALRT.checkContext("alGenBuffers"); + int pos = buffers.position(); + org.lwjgl.openal.AL10.alGenBuffers(buffers); + if (ctx != null) { + for (int i = pos; i < buffers.limit(); i++) { + int b = buffers.get(i); + if (b != 0) { + ctx.buffers.put(b, new ALObjects.Buffer(b, ctx)); + } + } + } + } + + public static void alGenBuffers(int[] buffers) { + ALContext ctx = ALRT.checkContext("alGenBuffers"); + org.lwjgl.openal.AL10.alGenBuffers(buffers); + if (ctx != null) { + for (int b : buffers) { + if (b != 0) { + ctx.buffers.put(b, new ALObjects.Buffer(b, ctx)); + } + } + } + } + + public static void alDeleteBuffers(int buffer) { + ALContext ctx = ALRT.checkContext("alDeleteBuffers"); + if (ctx != null && buffer != 0) { + deleteBufferCheck(ctx, buffer); + } + org.lwjgl.openal.AL10.alDeleteBuffers(buffer); + } + + public static void alDeleteBuffers(IntBuffer buffers) { + ALContext ctx = ALRT.checkContext("alDeleteBuffers"); + if (ctx != null) { + int pos = buffers.position(); + for (int i = pos; i < buffers.limit(); i++) { + int b = buffers.get(i); + if (b != 0) { + deleteBufferCheck(ctx, b); + } + } + } + org.lwjgl.openal.AL10.alDeleteBuffers(buffers); + } + + public static void alDeleteBuffers(int[] buffers) { + ALContext ctx = ALRT.checkContext("alDeleteBuffers"); + if (ctx != null) { + for (int b : buffers) { + if (b != 0) { + deleteBufferCheck(ctx, b); + } + } + } + org.lwjgl.openal.AL10.alDeleteBuffers(buffers); + } + + private static void deleteBufferCheck(ALContext ctx, int buffer) { + ALObjects.Buffer b = ctx.buffers.get(buffer); + if (b == null) { + RT.throwISEOrLogError("alDeleteBuffers: unknown buffer " + buffer); + return; + } + if (b.state == ResourceState.DELETED) { + RT.throwISEOrLogError("alDeleteBuffers: buffer " + buffer + " already deleted (double-free)" + + ALRT.deletionInfo(b.deletionSite) + ALRT.creationInfo(b.creationSite)); + return; + } + if (!b.attachedSources.isEmpty()) { + RT.throwISEOrLogError("alDeleteBuffers: buffer " + buffer + " is still attached to " + + b.attachedSources.size() + " source(s)"); + } + b.state = ResourceState.DELETED; + if (Properties.STRICT.enabled) { + b.deletionSite = new Throwable("Buffer deleted here"); + } + } + + public static boolean alIsBuffer(int buffer) { + ALRT.checkContext("alIsBuffer"); + return org.lwjgl.openal.AL10.alIsBuffer(buffer); + } + + public static void alBufferData(int buffer, int format, ByteBuffer data, int frequency) { + ALContext ctx = ALRT.checkContext("alBufferData"); + checkBufferData(ctx, buffer); + org.lwjgl.openal.AL10.alBufferData(buffer, format, data, frequency); + } + + public static void alBufferData(int buffer, int format, ShortBuffer data, int frequency) { + ALContext ctx = ALRT.checkContext("alBufferData"); + checkBufferData(ctx, buffer); + org.lwjgl.openal.AL10.alBufferData(buffer, format, data, frequency); + } + + public static void alBufferData(int buffer, int format, IntBuffer data, int frequency) { + ALContext ctx = ALRT.checkContext("alBufferData"); + checkBufferData(ctx, buffer); + org.lwjgl.openal.AL10.alBufferData(buffer, format, data, frequency); + } + + public static void alBufferData(int buffer, int format, FloatBuffer data, int frequency) { + ALContext ctx = ALRT.checkContext("alBufferData"); + checkBufferData(ctx, buffer); + org.lwjgl.openal.AL10.alBufferData(buffer, format, data, frequency); + } + + public static void alBufferData(int buffer, int format, short[] data, int frequency) { + ALContext ctx = ALRT.checkContext("alBufferData"); + checkBufferData(ctx, buffer); + org.lwjgl.openal.AL10.alBufferData(buffer, format, data, frequency); + } + + public static void alBufferData(int buffer, int format, int[] data, int frequency) { + ALContext ctx = ALRT.checkContext("alBufferData"); + checkBufferData(ctx, buffer); + org.lwjgl.openal.AL10.alBufferData(buffer, format, data, frequency); + } + + public static void alBufferData(int buffer, int format, float[] data, int frequency) { + ALContext ctx = ALRT.checkContext("alBufferData"); + checkBufferData(ctx, buffer); + org.lwjgl.openal.AL10.alBufferData(buffer, format, data, frequency); + } + + private static void checkBufferData(ALContext ctx, int buffer) { + ALObjects.Buffer b = ALRT.checkBuffer(ctx, buffer, "alBufferData"); + if (b != null && !b.attachedSources.isEmpty()) { + RT.throwISEOrLogError("alBufferData: modifying buffer " + buffer + + " while attached to source(s) is undefined behavior in OpenAL"); + } + } + + public static float alGetBufferf(int buffer, int param) { + ALContext ctx = ALRT.checkContext("alGetBufferf"); + ALRT.checkBuffer(ctx, buffer, "alGetBufferf"); + return org.lwjgl.openal.AL10.alGetBufferf(buffer, param); + } + + public static void alGetBufferf(int buffer, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alGetBufferf"); + ALRT.checkBuffer(ctx, buffer, "alGetBufferf"); + org.lwjgl.openal.AL10.alGetBufferf(buffer, param, values); + } + + public static void alGetBufferf(int buffer, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alGetBufferf"); + ALRT.checkBuffer(ctx, buffer, "alGetBufferf"); + org.lwjgl.openal.AL10.alGetBufferf(buffer, param, values); + } + + public static int alGetBufferi(int buffer, int param) { + ALContext ctx = ALRT.checkContext("alGetBufferi"); + ALRT.checkBuffer(ctx, buffer, "alGetBufferi"); + return org.lwjgl.openal.AL10.alGetBufferi(buffer, param); + } + + public static void alGetBufferi(int buffer, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alGetBufferi"); + ALRT.checkBuffer(ctx, buffer, "alGetBufferi"); + org.lwjgl.openal.AL10.alGetBufferi(buffer, param, values); + } + + public static void alGetBufferi(int buffer, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alGetBufferi"); + ALRT.checkBuffer(ctx, buffer, "alGetBufferi"); + org.lwjgl.openal.AL10.alGetBufferi(buffer, param, values); + } + + /* --- Source Management --- */ + + public static int alGenSources() { + ALContext ctx = ALRT.checkContext("alGenSources"); + int source = org.lwjgl.openal.AL10.alGenSources(); + if (ctx != null && source != 0) { + ctx.sources.put(source, new ALObjects.Source(source, ctx)); + } + return source; + } + + public static void alGenSources(IntBuffer sources) { + ALContext ctx = ALRT.checkContext("alGenSources"); + int pos = sources.position(); + org.lwjgl.openal.AL10.alGenSources(sources); + if (ctx != null) { + for (int i = pos; i < sources.limit(); i++) { + int s = sources.get(i); + if (s != 0) { + ctx.sources.put(s, new ALObjects.Source(s, ctx)); + } + } + } + } + + public static void alGenSources(int[] sources) { + ALContext ctx = ALRT.checkContext("alGenSources"); + org.lwjgl.openal.AL10.alGenSources(sources); + if (ctx != null) { + for (int s : sources) { + if (s != 0) { + ctx.sources.put(s, new ALObjects.Source(s, ctx)); + } + } + } + } + + public static void alDeleteSources(int source) { + ALContext ctx = ALRT.checkContext("alDeleteSources"); + if (ctx != null && source != 0) { + deleteSourceCheck(ctx, source); + } + org.lwjgl.openal.AL10.alDeleteSources(source); + } + + public static void alDeleteSources(IntBuffer sources) { + ALContext ctx = ALRT.checkContext("alDeleteSources"); + if (ctx != null) { + int pos = sources.position(); + for (int i = pos; i < sources.limit(); i++) { + int s = sources.get(i); + if (s != 0) { + deleteSourceCheck(ctx, s); + } + } + } + org.lwjgl.openal.AL10.alDeleteSources(sources); + } + + public static void alDeleteSources(int[] sources) { + ALContext ctx = ALRT.checkContext("alDeleteSources"); + if (ctx != null) { + for (int s : sources) { + if (s != 0) { + deleteSourceCheck(ctx, s); + } + } + } + org.lwjgl.openal.AL10.alDeleteSources(sources); + } + + private static void deleteSourceCheck(ALContext ctx, int source) { + ALObjects.Source s = ctx.sources.get(source); + if (s == null) { + RT.throwISEOrLogError("alDeleteSources: unknown source " + source); + return; + } + if (s.state == ResourceState.DELETED) { + RT.throwISEOrLogError("alDeleteSources: source " + source + " already deleted (double-free)" + + ALRT.deletionInfo(s.deletionSite) + ALRT.creationInfo(s.creationSite)); + return; + } + if (s.attachedBuffer != null) { + s.attachedBuffer.attachedSources.remove(s); + s.attachedBuffer = null; + } + s.state = ResourceState.DELETED; + if (Properties.STRICT.enabled) { + s.deletionSite = new Throwable("Source deleted here"); + } + } + + public static boolean alIsSource(int source) { + ALRT.checkContext("alIsSource"); + return org.lwjgl.openal.AL10.alIsSource(source); + } + + public static void alSourcei(int source, int param, int value) { + ALContext ctx = ALRT.checkContext("alSourcei"); + ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourcei"); + if (param == org.lwjgl.openal.AL10.AL_BUFFER) { + if (value != 0) { + ALObjects.Buffer b = ALRT.checkBuffer(ctx, value, "alSourcei(AL_BUFFER)"); + if (s != null && b != null) { + if (s.attachedBuffer != null) { + s.attachedBuffer.attachedSources.remove(s); + } + s.attachedBuffer = b; + b.attachedSources.add(s); + } + } else if (s != null && s.attachedBuffer != null) { + s.attachedBuffer.attachedSources.remove(s); + s.attachedBuffer = null; + } + } + org.lwjgl.openal.AL10.alSourcei(source, param, value); + } + + public static void alSourcef(int source, int param, float value) { + ALContext ctx = ALRT.checkContext("alSourcef"); + ALRT.checkSource(ctx, source, "alSourcef"); + org.lwjgl.openal.AL10.alSourcef(source, param, value); + } + + public static void alSource3f(int source, int param, float v1, float v2, float v3) { + ALContext ctx = ALRT.checkContext("alSource3f"); + ALRT.checkSource(ctx, source, "alSource3f"); + org.lwjgl.openal.AL10.alSource3f(source, param, v1, v2, v3); + } + + public static void alSourcefv(int source, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alSourcefv"); + ALRT.checkSource(ctx, source, "alSourcefv"); + org.lwjgl.openal.AL10.alSourcefv(source, param, values); + } + + public static void alSourcefv(int source, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alSourcefv"); + ALRT.checkSource(ctx, source, "alSourcefv"); + org.lwjgl.openal.AL10.alSourcefv(source, param, values); + } + + public static float alGetSourcef(int source, int param) { + ALContext ctx = ALRT.checkContext("alGetSourcef"); + ALRT.checkSource(ctx, source, "alGetSourcef"); + return org.lwjgl.openal.AL10.alGetSourcef(source, param); + } + + public static void alGetSourcef(int source, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alGetSourcef"); + ALRT.checkSource(ctx, source, "alGetSourcef"); + org.lwjgl.openal.AL10.alGetSourcef(source, param, values); + } + + public static void alGetSourcef(int source, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alGetSourcef"); + ALRT.checkSource(ctx, source, "alGetSourcef"); + org.lwjgl.openal.AL10.alGetSourcef(source, param, values); + } + + public static void alGetSource3f(int source, int param, FloatBuffer v1, FloatBuffer v2, FloatBuffer v3) { + ALContext ctx = ALRT.checkContext("alGetSource3f"); + ALRT.checkSource(ctx, source, "alGetSource3f"); + org.lwjgl.openal.AL10.alGetSource3f(source, param, v1, v2, v3); + } + + public static void alGetSource3f(int source, int param, float[] v1, float[] v2, float[] v3) { + ALContext ctx = ALRT.checkContext("alGetSource3f"); + ALRT.checkSource(ctx, source, "alGetSource3f"); + org.lwjgl.openal.AL10.alGetSource3f(source, param, v1, v2, v3); + } + + public static void alGetSourcefv(int source, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alGetSourcefv"); + ALRT.checkSource(ctx, source, "alGetSourcefv"); + org.lwjgl.openal.AL10.alGetSourcefv(source, param, values); + } + + public static void alGetSourcefv(int source, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alGetSourcefv"); + ALRT.checkSource(ctx, source, "alGetSourcefv"); + org.lwjgl.openal.AL10.alGetSourcefv(source, param, values); + } + + public static int alGetSourcei(int source, int param) { + ALContext ctx = ALRT.checkContext("alGetSourcei"); + ALRT.checkSource(ctx, source, "alGetSourcei"); + return org.lwjgl.openal.AL10.alGetSourcei(source, param); + } + + public static void alGetSourcei(int source, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alGetSourcei"); + ALRT.checkSource(ctx, source, "alGetSourcei"); + org.lwjgl.openal.AL10.alGetSourcei(source, param, values); + } + + public static void alGetSourcei(int source, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alGetSourcei"); + ALRT.checkSource(ctx, source, "alGetSourcei"); + org.lwjgl.openal.AL10.alGetSourcei(source, param, values); + } + + public static void alGetSourceiv(int source, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alGetSourceiv"); + ALRT.checkSource(ctx, source, "alGetSourceiv"); + org.lwjgl.openal.AL10.alGetSourceiv(source, param, values); + } + + public static void alGetSourceiv(int source, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alGetSourceiv"); + ALRT.checkSource(ctx, source, "alGetSourceiv"); + org.lwjgl.openal.AL10.alGetSourceiv(source, param, values); + } + + /* --- Source Playback Controls --- */ + + public static void alSourcePlay(int source) { + ALContext ctx = ALRT.checkContext("alSourcePlay"); + ALRT.checkSource(ctx, source, "alSourcePlay"); + org.lwjgl.openal.AL10.alSourcePlay(source); + } + + public static void alSourcePause(int source) { + ALContext ctx = ALRT.checkContext("alSourcePause"); + ALRT.checkSource(ctx, source, "alSourcePause"); + org.lwjgl.openal.AL10.alSourcePause(source); + } + + public static void alSourceStop(int source) { + ALContext ctx = ALRT.checkContext("alSourceStop"); + ALRT.checkSource(ctx, source, "alSourceStop"); + org.lwjgl.openal.AL10.alSourceStop(source); + } + + public static void alSourceRewind(int source) { + ALContext ctx = ALRT.checkContext("alSourceRewind"); + ALRT.checkSource(ctx, source, "alSourceRewind"); + org.lwjgl.openal.AL10.alSourceRewind(source); + } + + public static void alSourcePlayv(IntBuffer sources) { + ALContext ctx = ALRT.checkContext("alSourcePlayv"); + if (ctx != null) { + int pos = sources.position(); + for (int i = pos; i < sources.limit(); i++) { + ALRT.checkSource(ctx, sources.get(i), "alSourcePlayv"); + } + } + org.lwjgl.openal.AL10.alSourcePlayv(sources); + } + + public static void alSourcePlayv(int[] sources) { + ALContext ctx = ALRT.checkContext("alSourcePlayv"); + if (ctx != null) { + for (int s : sources) { + ALRT.checkSource(ctx, s, "alSourcePlayv"); + } + } + org.lwjgl.openal.AL10.alSourcePlayv(sources); + } + + public static void alSourcePausev(IntBuffer sources) { + ALContext ctx = ALRT.checkContext("alSourcePausev"); + if (ctx != null) { + int pos = sources.position(); + for (int i = pos; i < sources.limit(); i++) { + ALRT.checkSource(ctx, sources.get(i), "alSourcePausev"); + } + } + org.lwjgl.openal.AL10.alSourcePausev(sources); + } + + public static void alSourcePausev(int[] sources) { + ALContext ctx = ALRT.checkContext("alSourcePausev"); + if (ctx != null) { + for (int s : sources) { + ALRT.checkSource(ctx, s, "alSourcePausev"); + } + } + org.lwjgl.openal.AL10.alSourcePausev(sources); + } + + public static void alSourceStopv(IntBuffer sources) { + ALContext ctx = ALRT.checkContext("alSourceStopv"); + if (ctx != null) { + int pos = sources.position(); + for (int i = pos; i < sources.limit(); i++) { + ALRT.checkSource(ctx, sources.get(i), "alSourceStopv"); + } + } + org.lwjgl.openal.AL10.alSourceStopv(sources); + } + + public static void alSourceStopv(int[] sources) { + ALContext ctx = ALRT.checkContext("alSourceStopv"); + if (ctx != null) { + for (int s : sources) { + ALRT.checkSource(ctx, s, "alSourceStopv"); + } + } + org.lwjgl.openal.AL10.alSourceStopv(sources); + } + + public static void alSourceRewindv(IntBuffer sources) { + ALContext ctx = ALRT.checkContext("alSourceRewindv"); + if (ctx != null) { + int pos = sources.position(); + for (int i = pos; i < sources.limit(); i++) { + ALRT.checkSource(ctx, sources.get(i), "alSourceRewindv"); + } + } + org.lwjgl.openal.AL10.alSourceRewindv(sources); + } + + public static void alSourceRewindv(int[] sources) { + ALContext ctx = ALRT.checkContext("alSourceRewindv"); + if (ctx != null) { + for (int s : sources) { + ALRT.checkSource(ctx, s, "alSourceRewindv"); + } + } + org.lwjgl.openal.AL10.alSourceRewindv(sources); + } + + /* --- Buffer Queuing --- */ + + public static void alSourceQueueBuffers(int source, int buffer) { + ALContext ctx = ALRT.checkContext("alSourceQueueBuffers"); + ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourceQueueBuffers"); + ALObjects.Buffer b = ALRT.checkBuffer(ctx, buffer, "alSourceQueueBuffers"); + if (s != null && b != null) { + b.attachedSources.add(s); + } + org.lwjgl.openal.AL10.alSourceQueueBuffers(source, buffer); + } + + public static void alSourceQueueBuffers(int source, IntBuffer buffers) { + ALContext ctx = ALRT.checkContext("alSourceQueueBuffers"); + ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourceQueueBuffers"); + if (ctx != null && s != null) { + int pos = buffers.position(); + for (int i = pos; i < buffers.limit(); i++) { + ALObjects.Buffer b = ALRT.checkBuffer(ctx, buffers.get(i), "alSourceQueueBuffers"); + if (b != null) { + b.attachedSources.add(s); + } + } + } + org.lwjgl.openal.AL10.alSourceQueueBuffers(source, buffers); + } + + public static void alSourceQueueBuffers(int source, int[] buffers) { + ALContext ctx = ALRT.checkContext("alSourceQueueBuffers"); + ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourceQueueBuffers"); + if (ctx != null && s != null) { + for (int bufId : buffers) { + ALObjects.Buffer b = ALRT.checkBuffer(ctx, bufId, "alSourceQueueBuffers"); + if (b != null) { + b.attachedSources.add(s); + } + } + } + org.lwjgl.openal.AL10.alSourceQueueBuffers(source, buffers); + } + + public static int alSourceUnqueueBuffers(int source) { + ALContext ctx = ALRT.checkContext("alSourceUnqueueBuffers"); + ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourceUnqueueBuffers"); + int buffer = org.lwjgl.openal.AL10.alSourceUnqueueBuffers(source); + if (ctx != null && s != null && buffer != 0) { + ALObjects.Buffer b = ctx.buffers.get(buffer); + if (b != null) { + b.attachedSources.remove(s); + } + } + return buffer; + } + + public static void alSourceUnqueueBuffers(int source, IntBuffer buffers) { + ALContext ctx = ALRT.checkContext("alSourceUnqueueBuffers"); + ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourceUnqueueBuffers"); + int pos = buffers.position(); + org.lwjgl.openal.AL10.alSourceUnqueueBuffers(source, buffers); + if (ctx != null && s != null) { + for (int i = pos; i < buffers.limit(); i++) { + int bufId = buffers.get(i); + ALObjects.Buffer b = ctx.buffers.get(bufId); + if (b != null) { + b.attachedSources.remove(s); + } + } + } + } + + public static void alSourceUnqueueBuffers(int source, int[] buffers) { + ALContext ctx = ALRT.checkContext("alSourceUnqueueBuffers"); + ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourceUnqueueBuffers"); + org.lwjgl.openal.AL10.alSourceUnqueueBuffers(source, buffers); + if (ctx != null && s != null) { + for (int bufId : buffers) { + ALObjects.Buffer b = ctx.buffers.get(bufId); + if (b != null) { + b.attachedSources.remove(s); + } + } + } + } + + /* --- Listener Parameters --- */ + + public static void alListenerf(int param, float value) { + ALRT.checkContext("alListenerf"); + org.lwjgl.openal.AL10.alListenerf(param, value); + } + + public static void alListener3f(int param, float v1, float v2, float v3) { + ALRT.checkContext("alListener3f"); + org.lwjgl.openal.AL10.alListener3f(param, v1, v2, v3); + } + + public static void alListenerfv(int param, FloatBuffer values) { + ALRT.checkContext("alListenerfv"); + org.lwjgl.openal.AL10.alListenerfv(param, values); + } + + public static void alListenerfv(int param, float[] values) { + ALRT.checkContext("alListenerfv"); + org.lwjgl.openal.AL10.alListenerfv(param, values); + } + + public static void alListeneri(int param, int value) { + ALRT.checkContext("alListeneri"); + org.lwjgl.openal.AL10.alListeneri(param, value); + } + + public static float alGetListenerf(int param) { + ALRT.checkContext("alGetListenerf"); + return org.lwjgl.openal.AL10.alGetListenerf(param); + } + + public static void alGetListenerf(int param, FloatBuffer values) { + ALRT.checkContext("alGetListenerf"); + org.lwjgl.openal.AL10.alGetListenerf(param, values); + } + + public static void alGetListenerf(int param, float[] values) { + ALRT.checkContext("alGetListenerf"); + org.lwjgl.openal.AL10.alGetListenerf(param, values); + } + + public static void alGetListener3f(int param, FloatBuffer v1, FloatBuffer v2, FloatBuffer v3) { + ALRT.checkContext("alGetListener3f"); + org.lwjgl.openal.AL10.alGetListener3f(param, v1, v2, v3); + } + + public static void alGetListener3f(int param, float[] v1, float[] v2, float[] v3) { + ALRT.checkContext("alGetListener3f"); + org.lwjgl.openal.AL10.alGetListener3f(param, v1, v2, v3); + } + + public static void alGetListenerfv(int param, FloatBuffer values) { + ALRT.checkContext("alGetListenerfv"); + org.lwjgl.openal.AL10.alGetListenerfv(param, values); + } + + public static void alGetListenerfv(int param, float[] values) { + ALRT.checkContext("alGetListenerfv"); + org.lwjgl.openal.AL10.alGetListenerfv(param, values); + } + + public static int alGetListeneri(int param) { + ALRT.checkContext("alGetListeneri"); + return org.lwjgl.openal.AL10.alGetListeneri(param); + } + + public static void alGetListeneri(int param, IntBuffer values) { + ALRT.checkContext("alGetListeneri"); + org.lwjgl.openal.AL10.alGetListeneri(param, values); + } + + public static void alGetListeneri(int param, int[] values) { + ALRT.checkContext("alGetListeneri"); + org.lwjgl.openal.AL10.alGetListeneri(param, values); + } + + /* --- Global Queries & State --- */ + + public static void alEnable(int capability) { + ALRT.checkContext("alEnable"); + org.lwjgl.openal.AL10.alEnable(capability); + } + + public static void alDisable(int capability) { + ALRT.checkContext("alDisable"); + org.lwjgl.openal.AL10.alDisable(capability); + } + + public static boolean alIsEnabled(int capability) { + ALRT.checkContext("alIsEnabled"); + return org.lwjgl.openal.AL10.alIsEnabled(capability); + } + + public static boolean alGetBoolean(int param) { + ALRT.checkContext("alGetBoolean"); + return org.lwjgl.openal.AL10.alGetBoolean(param); + } + + public static void alGetBooleanv(int param, ByteBuffer data) { + ALRT.checkContext("alGetBooleanv"); + org.lwjgl.openal.AL10.alGetBooleanv(param, data); + } + + public static int alGetInteger(int param) { + ALRT.checkContext("alGetInteger"); + return org.lwjgl.openal.AL10.alGetInteger(param); + } + + public static void alGetIntegerv(int param, IntBuffer data) { + ALRT.checkContext("alGetIntegerv"); + org.lwjgl.openal.AL10.alGetIntegerv(param, data); + } + + public static void alGetIntegerv(int param, int[] data) { + ALRT.checkContext("alGetIntegerv"); + org.lwjgl.openal.AL10.alGetIntegerv(param, data); + } + + public static float alGetFloat(int param) { + ALRT.checkContext("alGetFloat"); + return org.lwjgl.openal.AL10.alGetFloat(param); + } + + public static void alGetFloatv(int param, FloatBuffer data) { + ALRT.checkContext("alGetFloatv"); + org.lwjgl.openal.AL10.alGetFloatv(param, data); + } + + public static void alGetFloatv(int param, float[] data) { + ALRT.checkContext("alGetFloatv"); + org.lwjgl.openal.AL10.alGetFloatv(param, data); + } + + public static double alGetDouble(int param) { + ALRT.checkContext("alGetDouble"); + return org.lwjgl.openal.AL10.alGetDouble(param); + } + + public static void alGetDoublev(int param, DoubleBuffer data) { + ALRT.checkContext("alGetDoublev"); + org.lwjgl.openal.AL10.alGetDoublev(param, data); + } + + public static void alGetDoublev(int param, double[] data) { + ALRT.checkContext("alGetDoublev"); + org.lwjgl.openal.AL10.alGetDoublev(param, data); + } + + public static String alGetString(int param) { + ALRT.checkContext("alGetString"); + return org.lwjgl.openal.AL10.alGetString(param); + } + + public static void alDistanceModel(int value) { + ALRT.checkContext("alDistanceModel"); + org.lwjgl.openal.AL10.alDistanceModel(value); + } + + public static void alDopplerFactor(float value) { + ALRT.checkContext("alDopplerFactor"); + org.lwjgl.openal.AL10.alDopplerFactor(value); + } + + public static void alDopplerVelocity(float value) { + ALRT.checkContext("alDopplerVelocity"); + org.lwjgl.openal.AL10.alDopplerVelocity(value); + } +} diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/AL11.java b/src/org/lwjglx/debug/org/lwjgl/openal/AL11.java new file mode 100644 index 0000000..b15cadd --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/openal/AL11.java @@ -0,0 +1,184 @@ +package org.lwjglx.debug.org.lwjgl.openal; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import org.lwjglx.debug.openal.ALContext; +import org.lwjglx.debug.openal.ALRT; + +public class AL11 { + + /* --- Listener Parameters --- */ + + public static void alListener3i(int param, int v1, int v2, int v3) { + ALRT.checkContext("alListener3i"); + org.lwjgl.openal.AL11.alListener3i(param, v1, v2, v3); + } + + public static void alGetListener3i(int param, IntBuffer v1, IntBuffer v2, IntBuffer v3) { + ALRT.checkContext("alGetListener3i"); + org.lwjgl.openal.AL11.alGetListener3i(param, v1, v2, v3); + } + + public static void alGetListener3i(int param, int[] v1, int[] v2, int[] v3) { + ALRT.checkContext("alGetListener3i"); + org.lwjgl.openal.AL11.alGetListener3i(param, v1, v2, v3); + } + + public static void alListeneriv(int param, IntBuffer values) { + ALRT.checkContext("alListeneriv"); + org.lwjgl.openal.AL11.alListeneriv(param, values); + } + + public static void alListeneriv(int param, int[] values) { + ALRT.checkContext("alListeneriv"); + org.lwjgl.openal.AL11.alListeneriv(param, values); + } + + public static void alGetListeneriv(int param, IntBuffer values) { + ALRT.checkContext("alGetListeneriv"); + org.lwjgl.openal.AL11.alGetListeneriv(param, values); + } + + public static void alGetListeneriv(int param, int[] values) { + ALRT.checkContext("alGetListeneriv"); + org.lwjgl.openal.AL11.alGetListeneriv(param, values); + } + + /* --- Source Parameters --- */ + + public static void alSource3i(int source, int param, int v1, int v2, int v3) { + ALContext ctx = ALRT.checkContext("alSource3i"); + ALRT.checkSource(ctx, source, "alSource3i"); + org.lwjgl.openal.AL11.alSource3i(source, param, v1, v2, v3); + } + + public static void alGetSource3i(int source, int param, IntBuffer v1, IntBuffer v2, IntBuffer v3) { + ALContext ctx = ALRT.checkContext("alGetSource3i"); + ALRT.checkSource(ctx, source, "alGetSource3i"); + org.lwjgl.openal.AL11.alGetSource3i(source, param, v1, v2, v3); + } + + public static void alGetSource3i(int source, int param, int[] v1, int[] v2, int[] v3) { + ALContext ctx = ALRT.checkContext("alGetSource3i"); + ALRT.checkSource(ctx, source, "alGetSource3i"); + org.lwjgl.openal.AL11.alGetSource3i(source, param, v1, v2, v3); + } + + public static void alSourceiv(int source, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alSourceiv"); + ALRT.checkSource(ctx, source, "alSourceiv"); + org.lwjgl.openal.AL11.alSourceiv(source, param, values); + } + + public static void alSourceiv(int source, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alSourceiv"); + ALRT.checkSource(ctx, source, "alSourceiv"); + org.lwjgl.openal.AL11.alSourceiv(source, param, values); + } + + /* --- Buffer Parameters --- */ + + public static void alBufferf(int buffer, int param, float value) { + ALContext ctx = ALRT.checkContext("alBufferf"); + ALRT.checkBuffer(ctx, buffer, "alBufferf"); + org.lwjgl.openal.AL11.alBufferf(buffer, param, value); + } + + public static void alBuffer3f(int buffer, int param, float v1, float v2, float v3) { + ALContext ctx = ALRT.checkContext("alBuffer3f"); + ALRT.checkBuffer(ctx, buffer, "alBuffer3f"); + org.lwjgl.openal.AL11.alBuffer3f(buffer, param, v1, v2, v3); + } + + public static void alBufferfv(int buffer, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alBufferfv"); + ALRT.checkBuffer(ctx, buffer, "alBufferfv"); + org.lwjgl.openal.AL11.alBufferfv(buffer, param, values); + } + + public static void alBufferfv(int buffer, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alBufferfv"); + ALRT.checkBuffer(ctx, buffer, "alBufferfv"); + org.lwjgl.openal.AL11.alBufferfv(buffer, param, values); + } + + public static void alBufferi(int buffer, int param, int value) { + ALContext ctx = ALRT.checkContext("alBufferi"); + ALRT.checkBuffer(ctx, buffer, "alBufferi"); + org.lwjgl.openal.AL11.alBufferi(buffer, param, value); + } + + public static void alBuffer3i(int buffer, int param, int v1, int v2, int v3) { + ALContext ctx = ALRT.checkContext("alBuffer3i"); + ALRT.checkBuffer(ctx, buffer, "alBuffer3i"); + org.lwjgl.openal.AL11.alBuffer3i(buffer, param, v1, v2, v3); + } + + public static void alBufferiv(int buffer, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alBufferiv"); + ALRT.checkBuffer(ctx, buffer, "alBufferiv"); + org.lwjgl.openal.AL11.alBufferiv(buffer, param, values); + } + + public static void alBufferiv(int buffer, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alBufferiv"); + ALRT.checkBuffer(ctx, buffer, "alBufferiv"); + org.lwjgl.openal.AL11.alBufferiv(buffer, param, values); + } + + public static void alGetBuffer3i(int buffer, int param, IntBuffer v1, IntBuffer v2, IntBuffer v3) { + ALContext ctx = ALRT.checkContext("alGetBuffer3i"); + ALRT.checkBuffer(ctx, buffer, "alGetBuffer3i"); + org.lwjgl.openal.AL11.alGetBuffer3i(buffer, param, v1, v2, v3); + } + + public static void alGetBuffer3i(int buffer, int param, int[] v1, int[] v2, int[] v3) { + ALContext ctx = ALRT.checkContext("alGetBuffer3i"); + ALRT.checkBuffer(ctx, buffer, "alGetBuffer3i"); + org.lwjgl.openal.AL11.alGetBuffer3i(buffer, param, v1, v2, v3); + } + + public static void alGetBufferiv(int buffer, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alGetBufferiv"); + ALRT.checkBuffer(ctx, buffer, "alGetBufferiv"); + org.lwjgl.openal.AL11.alGetBufferiv(buffer, param, values); + } + + public static void alGetBufferiv(int buffer, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alGetBufferiv"); + ALRT.checkBuffer(ctx, buffer, "alGetBufferiv"); + org.lwjgl.openal.AL11.alGetBufferiv(buffer, param, values); + } + + public static void alGetBuffer3f(int buffer, int param, FloatBuffer v1, FloatBuffer v2, FloatBuffer v3) { + ALContext ctx = ALRT.checkContext("alGetBuffer3f"); + ALRT.checkBuffer(ctx, buffer, "alGetBuffer3f"); + org.lwjgl.openal.AL11.alGetBuffer3f(buffer, param, v1, v2, v3); + } + + public static void alGetBuffer3f(int buffer, int param, float[] v1, float[] v2, float[] v3) { + ALContext ctx = ALRT.checkContext("alGetBuffer3f"); + ALRT.checkBuffer(ctx, buffer, "alGetBuffer3f"); + org.lwjgl.openal.AL11.alGetBuffer3f(buffer, param, v1, v2, v3); + } + + public static void alGetBufferfv(int buffer, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alGetBufferfv"); + ALRT.checkBuffer(ctx, buffer, "alGetBufferfv"); + org.lwjgl.openal.AL11.alGetBufferfv(buffer, param, values); + } + + public static void alGetBufferfv(int buffer, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alGetBufferfv"); + ALRT.checkBuffer(ctx, buffer, "alGetBufferfv"); + org.lwjgl.openal.AL11.alGetBufferfv(buffer, param, values); + } + + /* --- Global Queries --- */ + + public static void alSpeedOfSound(float value) { + ALRT.checkContext("alSpeedOfSound"); + org.lwjgl.openal.AL11.alSpeedOfSound(value); + } +} diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/ALC.java b/src/org/lwjglx/debug/org/lwjgl/openal/ALC.java new file mode 100644 index 0000000..f20e5f0 --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/openal/ALC.java @@ -0,0 +1,32 @@ +package org.lwjglx.debug.org.lwjgl.openal; + +import java.util.function.IntFunction; + +import org.lwjgl.PointerBuffer; +import org.lwjgl.openal.ALCCapabilities; +import org.lwjglx.debug.openal.ALDevice; +import org.lwjglx.debug.openal.ALRT; + +public class ALC { + + public static ALCCapabilities createCapabilities(long device) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "ALC.createCapabilities"); + return org.lwjgl.openal.ALC.createCapabilities(device); + } + + public static ALCCapabilities createCapabilities(long device, IntFunction bufferFactory) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "ALC.createCapabilities"); + return org.lwjgl.openal.ALC.createCapabilities(device, bufferFactory); + } + + public static void destroy() { + for (ALDevice dev : ALDevice.allDevices()) { + if (dev.state == org.lwjglx.debug.openal.ResourceState.ALIVE) { + org.lwjglx.debug.RT.throwISEOrLogError("ALC.destroy: device 0x" + Long.toHexString(dev.handle) + " was never closed"); + } + } + org.lwjgl.openal.ALC.destroy(); + } +} diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/ALC10.java b/src/org/lwjglx/debug/org/lwjgl/openal/ALC10.java new file mode 100644 index 0000000..e1db06e --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/openal/ALC10.java @@ -0,0 +1,286 @@ +package org.lwjglx.debug.org.lwjgl.openal; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import org.lwjglx.debug.RT; +import org.lwjglx.debug.openal.ALContext; +import org.lwjglx.debug.openal.ALDevice; +import org.lwjglx.debug.openal.ALObjects; +import org.lwjglx.debug.openal.ALRT; +import org.lwjglx.debug.openal.ResourceState; + +public class ALC10 { + + public static long alcOpenDevice(ByteBuffer devicename) { + long device = org.lwjgl.openal.ALC10.alcOpenDevice(devicename); + if (device != 0L) { + ALDevice.create(device); + } + return device; + } + + public static long alcOpenDevice(CharSequence devicename) { + long device = org.lwjgl.openal.ALC10.alcOpenDevice(devicename); + if (device != 0L) { + ALDevice.create(device); + } + return device; + } + + public static boolean alcCloseDevice(long device) { + if (device == 0L) { + return false; + } + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCloseDevice"); + if (dev != null) { + for (ALContext ctx : dev.contexts) { + if (ctx.state == ResourceState.ALIVE) { + RT.throwISEOrLogError("alcCloseDevice called on device 0x" + Long.toHexString(device) + + " while context 0x" + Long.toHexString(ctx.handle) + " is still alive"); + } + } + } + boolean res = org.lwjgl.openal.ALC10.alcCloseDevice(device); + if (res && dev != null) { + dev.state = ResourceState.DELETED; + } + return res; + } + + public static long alcCreateContext(long device, IntBuffer attrlist) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCreateContext"); + long context = org.lwjgl.openal.ALC10.alcCreateContext(device, attrlist); + if (context != 0L && dev != null) { + ALContext.create(context, dev); + } + return context; + } + + public static long alcCreateContext(long device, int[] attrlist) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCreateContext"); + long context = org.lwjgl.openal.ALC10.alcCreateContext(device, attrlist); + if (context != 0L && dev != null) { + ALContext.create(context, dev); + } + return context; + } + + public static boolean alcMakeContextCurrent(long context) { + if (context == 0L) { + ALContext.makeCurrent(null); + return org.lwjgl.openal.ALC10.alcMakeContextCurrent(0L); + } + ALContext ctx = ALContext.get(context); + if (ctx == null) { + RT.throwISEOrLogError("alcMakeContextCurrent: unknown context handle 0x" + Long.toHexString(context)); + } else if (ctx.state == ResourceState.DELETED) { + RT.throwISEOrLogError("alcMakeContextCurrent: context 0x" + Long.toHexString(context) + + " has already been destroyed (use-after-free)"); + } + boolean res = org.lwjgl.openal.ALC10.alcMakeContextCurrent(context); + if (res) { + ALContext.makeCurrent(ctx); + } + return res; + } + + public static void alcProcessContext(long context) { + ALContext ctx = ALContext.get(context); + if (ctx != null && ctx.state == ResourceState.DELETED) { + RT.throwISEOrLogError("alcProcessContext: context has been destroyed (use-after-free)"); + } + org.lwjgl.openal.ALC10.alcProcessContext(context); + } + + public static void alcSuspendContext(long context) { + ALContext ctx = ALContext.get(context); + if (ctx != null && ctx.state == ResourceState.DELETED) { + RT.throwISEOrLogError("alcSuspendContext: context has been destroyed (use-after-free)"); + } + org.lwjgl.openal.ALC10.alcSuspendContext(context); + } + + public static void alcDestroyContext(long context) { + if (context == 0L) { + return; + } + ALContext ctx = ALContext.get(context); + if (ctx == null) { + RT.throwISEOrLogError("alcDestroyContext: unknown context handle 0x" + Long.toHexString(context)); + org.lwjgl.openal.ALC10.alcDestroyContext(context); + return; + } + if (ctx.state == ResourceState.DELETED) { + RT.throwISEOrLogError("alcDestroyContext: context 0x" + Long.toHexString(context) + + " has already been destroyed (double-free)"); + return; + } + + try { + // Audit leaked resources + int liveBuffers = 0; + for (ALObjects.Buffer b : ctx.buffers.values()) { + if (b.state == ResourceState.ALIVE) { + liveBuffers++; + } + } + if (liveBuffers > 0) { + RT.throwISEOrLogError("alcDestroyContext: leaked " + liveBuffers + " buffer(s) in context 0x" + + Long.toHexString(context)); + } + + int liveSources = 0; + for (ALObjects.Source s : ctx.sources.values()) { + if (s.state == ResourceState.ALIVE) { + liveSources++; + } + } + if (liveSources > 0) { + RT.throwISEOrLogError("alcDestroyContext: leaked " + liveSources + " source(s) in context 0x" + + Long.toHexString(context)); + } + + int liveEffects = 0; + for (ALObjects.Effect e : ctx.effects.values()) { + if (e.state == ResourceState.ALIVE) { + liveEffects++; + } + } + if (liveEffects > 0) { + RT.throwISEOrLogError("alcDestroyContext: leaked " + liveEffects + " effect(s) in context 0x" + + Long.toHexString(context)); + } + + int liveFilters = 0; + for (ALObjects.Filter f : ctx.filters.values()) { + if (f.state == ResourceState.ALIVE) { + liveFilters++; + } + } + if (liveFilters > 0) { + RT.throwISEOrLogError("alcDestroyContext: leaked " + liveFilters + " filter(s) in context 0x" + + Long.toHexString(context)); + } + + int liveAuxSlots = 0; + for (ALObjects.AuxiliaryEffectSlot a : ctx.auxSlots.values()) { + if (a.state == ResourceState.ALIVE) { + liveAuxSlots++; + } + } + if (liveAuxSlots > 0) { + RT.throwISEOrLogError("alcDestroyContext: leaked " + liveAuxSlots + " auxiliary effect slot(s) in context 0x" + + Long.toHexString(context)); + } + } finally { + org.lwjgl.openal.ALC10.alcDestroyContext(context); + ctx.state = ResourceState.DELETED; + if (ALContext.currentContext() == ctx) { + ALContext.makeCurrent(null); + } + if (ctx.device != null) { + ctx.device.contexts.remove(ctx); + } + } + } + + public static long alcGetContextsDevice(long context) { + ALContext ctx = ALContext.get(context); + if (ctx != null && ctx.state == ResourceState.DELETED) { + RT.throwISEOrLogError("alcGetContextsDevice: context has been destroyed (use-after-free)"); + } + return org.lwjgl.openal.ALC10.alcGetContextsDevice(context); + } + + public static boolean alcIsExtensionPresent(long device, ByteBuffer extName) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcIsExtensionPresent"); + } + return org.lwjgl.openal.ALC10.alcIsExtensionPresent(device, extName); + } + + public static boolean alcIsExtensionPresent(long device, CharSequence extName) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcIsExtensionPresent"); + } + return org.lwjgl.openal.ALC10.alcIsExtensionPresent(device, extName); + } + + public static long alcGetProcAddress(long device, ByteBuffer funcName) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcGetProcAddress"); + } + return org.lwjgl.openal.ALC10.alcGetProcAddress(device, funcName); + } + + public static long alcGetProcAddress(long device, CharSequence funcName) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcGetProcAddress"); + } + return org.lwjgl.openal.ALC10.alcGetProcAddress(device, funcName); + } + + public static int alcGetEnumValue(long device, ByteBuffer enumName) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcGetEnumValue"); + } + return org.lwjgl.openal.ALC10.alcGetEnumValue(device, enumName); + } + + public static int alcGetEnumValue(long device, CharSequence enumName) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcGetEnumValue"); + } + return org.lwjgl.openal.ALC10.alcGetEnumValue(device, enumName); + } + + public static int alcGetError(long device) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcGetError"); + } + return org.lwjgl.openal.ALC10.alcGetError(device); + } + + public static String alcGetString(long device, int param) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcGetString"); + } + return org.lwjgl.openal.ALC10.alcGetString(device, param); + } + + public static void alcGetIntegerv(long device, int param, IntBuffer size) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcGetIntegerv"); + } + org.lwjgl.openal.ALC10.alcGetIntegerv(device, param, size); + } + + public static void alcGetIntegerv(long device, int param, int[] size) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcGetIntegerv"); + } + org.lwjgl.openal.ALC10.alcGetIntegerv(device, param, size); + } + + public static int alcGetInteger(long device, int param) { + if (device != 0L) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcGetInteger"); + } + return org.lwjgl.openal.ALC10.alcGetInteger(device, param); + } +} diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/ALC11.java b/src/org/lwjglx/debug/org/lwjgl/openal/ALC11.java new file mode 100644 index 0000000..d07af74 --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/openal/ALC11.java @@ -0,0 +1,96 @@ +package org.lwjglx.debug.org.lwjgl.openal; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import org.lwjglx.debug.openal.ALDevice; +import org.lwjglx.debug.openal.ALRT; +import org.lwjglx.debug.openal.ResourceState; + +public class ALC11 { + + public static long alcCaptureOpenDevice(ByteBuffer devicename, int frequency, int format, int buffersize) { + long device = org.lwjgl.openal.ALC11.alcCaptureOpenDevice(devicename, frequency, format, buffersize); + if (device != 0L) { + ALDevice.create(device); + } + return device; + } + + public static long alcCaptureOpenDevice(CharSequence devicename, int frequency, int format, int buffersize) { + long device = org.lwjgl.openal.ALC11.alcCaptureOpenDevice(devicename, frequency, format, buffersize); + if (device != 0L) { + ALDevice.create(device); + } + return device; + } + + public static boolean alcCaptureCloseDevice(long device) { + if (device == 0L) { + return false; + } + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCaptureCloseDevice"); + boolean res = org.lwjgl.openal.ALC11.alcCaptureCloseDevice(device); + if (res && dev != null) { + dev.state = ResourceState.DELETED; + } + return res; + } + + public static void alcCaptureStart(long device) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCaptureStart"); + org.lwjgl.openal.ALC11.alcCaptureStart(device); + } + + public static void alcCaptureStop(long device) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCaptureStop"); + org.lwjgl.openal.ALC11.alcCaptureStop(device); + } + + public static void alcCaptureSamples(long device, ByteBuffer buffer, int samples) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCaptureSamples"); + org.lwjgl.openal.ALC11.alcCaptureSamples(device, buffer, samples); + } + + public static void alcCaptureSamples(long device, ShortBuffer buffer, int samples) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCaptureSamples"); + org.lwjgl.openal.ALC11.alcCaptureSamples(device, buffer, samples); + } + + public static void alcCaptureSamples(long device, IntBuffer buffer, int samples) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCaptureSamples"); + org.lwjgl.openal.ALC11.alcCaptureSamples(device, buffer, samples); + } + + public static void alcCaptureSamples(long device, FloatBuffer buffer, int samples) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCaptureSamples"); + org.lwjgl.openal.ALC11.alcCaptureSamples(device, buffer, samples); + } + + public static void alcCaptureSamples(long device, short[] buffer, int samples) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCaptureSamples"); + org.lwjgl.openal.ALC11.alcCaptureSamples(device, buffer, samples); + } + + public static void alcCaptureSamples(long device, int[] buffer, int samples) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCaptureSamples"); + org.lwjgl.openal.ALC11.alcCaptureSamples(device, buffer, samples); + } + + public static void alcCaptureSamples(long device, float[] buffer, int samples) { + ALDevice dev = ALDevice.get(device); + ALRT.checkDevice(dev, device, "alcCaptureSamples"); + org.lwjgl.openal.ALC11.alcCaptureSamples(device, buffer, samples); + } +} diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/EXTEfx.java b/src/org/lwjglx/debug/org/lwjgl/openal/EXTEfx.java new file mode 100644 index 0000000..01ce64c --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/openal/EXTEfx.java @@ -0,0 +1,585 @@ +package org.lwjglx.debug.org.lwjgl.openal; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import org.lwjglx.debug.Properties; +import org.lwjglx.debug.RT; +import org.lwjglx.debug.openal.ALContext; +import org.lwjglx.debug.openal.ALObjects; +import org.lwjglx.debug.openal.ALRT; +import org.lwjglx.debug.openal.ResourceState; + +public class EXTEfx { + + /* --- Effect Objects --- */ + + public static int alGenEffects() { + ALContext ctx = ALRT.checkContext("alGenEffects"); + int effect = org.lwjgl.openal.EXTEfx.alGenEffects(); + if (ctx != null && effect != 0) { + ctx.effects.put(effect, new ALObjects.Effect(effect, ctx)); + } + return effect; + } + + public static void alGenEffects(IntBuffer effects) { + ALContext ctx = ALRT.checkContext("alGenEffects"); + int pos = effects.position(); + org.lwjgl.openal.EXTEfx.alGenEffects(effects); + if (ctx != null) { + for (int i = pos; i < effects.limit(); i++) { + int e = effects.get(i); + if (e != 0) { + ctx.effects.put(e, new ALObjects.Effect(e, ctx)); + } + } + } + } + + public static void alGenEffects(int[] effects) { + ALContext ctx = ALRT.checkContext("alGenEffects"); + org.lwjgl.openal.EXTEfx.alGenEffects(effects); + if (ctx != null) { + for (int e : effects) { + if (e != 0) { + ctx.effects.put(e, new ALObjects.Effect(e, ctx)); + } + } + } + } + + public static void alDeleteEffects(int effect) { + ALContext ctx = ALRT.checkContext("alDeleteEffects"); + if (ctx != null && effect != 0) { + deleteEffectCheck(ctx, effect); + } + org.lwjgl.openal.EXTEfx.alDeleteEffects(effect); + } + + public static void alDeleteEffects(IntBuffer effects) { + ALContext ctx = ALRT.checkContext("alDeleteEffects"); + if (ctx != null) { + int pos = effects.position(); + for (int i = pos; i < effects.limit(); i++) { + int e = effects.get(i); + if (e != 0) { + deleteEffectCheck(ctx, e); + } + } + } + org.lwjgl.openal.EXTEfx.alDeleteEffects(effects); + } + + public static void alDeleteEffects(int[] effects) { + ALContext ctx = ALRT.checkContext("alDeleteEffects"); + if (ctx != null) { + for (int e : effects) { + if (e != 0) { + deleteEffectCheck(ctx, e); + } + } + } + org.lwjgl.openal.EXTEfx.alDeleteEffects(effects); + } + + private static void deleteEffectCheck(ALContext ctx, int effect) { + ALObjects.Effect e = ctx.effects.get(effect); + if (e == null) { + RT.throwISEOrLogError("alDeleteEffects: unknown effect " + effect); + return; + } + if (e.state == ResourceState.DELETED) { + RT.throwISEOrLogError("alDeleteEffects: effect " + effect + " already deleted (double-free)" + + ALRT.deletionInfo(e.deletionSite) + ALRT.creationInfo(e.creationSite)); + return; + } + e.state = ResourceState.DELETED; + if (Properties.STRICT.enabled) { + e.deletionSite = new Throwable("Effect deleted here"); + } + } + + public static boolean alIsEffect(int effect) { + ALRT.checkContext("alIsEffect"); + return org.lwjgl.openal.EXTEfx.alIsEffect(effect); + } + + public static void alEffecti(int effect, int param, int value) { + ALContext ctx = ALRT.checkContext("alEffecti"); + ALRT.checkEffect(ctx, effect, "alEffecti"); + org.lwjgl.openal.EXTEfx.alEffecti(effect, param, value); + } + + public static void alEffectiv(int effect, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alEffectiv"); + ALRT.checkEffect(ctx, effect, "alEffectiv"); + org.lwjgl.openal.EXTEfx.alEffectiv(effect, param, values); + } + + public static void alEffectiv(int effect, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alEffectiv"); + ALRT.checkEffect(ctx, effect, "alEffectiv"); + org.lwjgl.openal.EXTEfx.alEffectiv(effect, param, values); + } + + public static void alEffectf(int effect, int param, float value) { + ALContext ctx = ALRT.checkContext("alEffectf"); + ALRT.checkEffect(ctx, effect, "alEffectf"); + org.lwjgl.openal.EXTEfx.alEffectf(effect, param, value); + } + + public static void alEffectfv(int effect, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alEffectfv"); + ALRT.checkEffect(ctx, effect, "alEffectfv"); + org.lwjgl.openal.EXTEfx.alEffectfv(effect, param, values); + } + + public static void alEffectfv(int effect, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alEffectfv"); + ALRT.checkEffect(ctx, effect, "alEffectfv"); + org.lwjgl.openal.EXTEfx.alEffectfv(effect, param, values); + } + + public static int alGetEffecti(int effect, int param) { + ALContext ctx = ALRT.checkContext("alGetEffecti"); + ALRT.checkEffect(ctx, effect, "alGetEffecti"); + return org.lwjgl.openal.EXTEfx.alGetEffecti(effect, param); + } + + public static void alGetEffecti(int effect, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alGetEffecti"); + ALRT.checkEffect(ctx, effect, "alGetEffecti"); + org.lwjgl.openal.EXTEfx.alGetEffecti(effect, param, values); + } + + public static void alGetEffecti(int effect, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alGetEffecti"); + ALRT.checkEffect(ctx, effect, "alGetEffecti"); + org.lwjgl.openal.EXTEfx.alGetEffecti(effect, param, values); + } + + public static void alGetEffectiv(int effect, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alGetEffectiv"); + ALRT.checkEffect(ctx, effect, "alGetEffectiv"); + org.lwjgl.openal.EXTEfx.alGetEffectiv(effect, param, values); + } + + public static void alGetEffectiv(int effect, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alGetEffectiv"); + ALRT.checkEffect(ctx, effect, "alGetEffectiv"); + org.lwjgl.openal.EXTEfx.alGetEffectiv(effect, param, values); + } + + public static float alGetEffectf(int effect, int param) { + ALContext ctx = ALRT.checkContext("alGetEffectf"); + ALRT.checkEffect(ctx, effect, "alGetEffectf"); + return org.lwjgl.openal.EXTEfx.alGetEffectf(effect, param); + } + + public static void alGetEffectf(int effect, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alGetEffectf"); + ALRT.checkEffect(ctx, effect, "alGetEffectf"); + org.lwjgl.openal.EXTEfx.alGetEffectf(effect, param, values); + } + + public static void alGetEffectf(int effect, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alGetEffectf"); + ALRT.checkEffect(ctx, effect, "alGetEffectf"); + org.lwjgl.openal.EXTEfx.alGetEffectf(effect, param, values); + } + + public static void alGetEffectfv(int effect, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alGetEffectfv"); + ALRT.checkEffect(ctx, effect, "alGetEffectfv"); + org.lwjgl.openal.EXTEfx.alGetEffectfv(effect, param, values); + } + + public static void alGetEffectfv(int effect, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alGetEffectfv"); + ALRT.checkEffect(ctx, effect, "alGetEffectfv"); + org.lwjgl.openal.EXTEfx.alGetEffectfv(effect, param, values); + } + + /* --- Filter Objects --- */ + + public static int alGenFilters() { + ALContext ctx = ALRT.checkContext("alGenFilters"); + int filter = org.lwjgl.openal.EXTEfx.alGenFilters(); + if (ctx != null && filter != 0) { + ctx.filters.put(filter, new ALObjects.Filter(filter, ctx)); + } + return filter; + } + + public static void alGenFilters(IntBuffer filters) { + ALContext ctx = ALRT.checkContext("alGenFilters"); + int pos = filters.position(); + org.lwjgl.openal.EXTEfx.alGenFilters(filters); + if (ctx != null) { + for (int i = pos; i < filters.limit(); i++) { + int f = filters.get(i); + if (f != 0) { + ctx.filters.put(f, new ALObjects.Filter(f, ctx)); + } + } + } + } + + public static void alGenFilters(int[] filters) { + ALContext ctx = ALRT.checkContext("alGenFilters"); + org.lwjgl.openal.EXTEfx.alGenFilters(filters); + if (ctx != null) { + for (int f : filters) { + if (f != 0) { + ctx.filters.put(f, new ALObjects.Filter(f, ctx)); + } + } + } + } + + public static void alDeleteFilters(int filter) { + ALContext ctx = ALRT.checkContext("alDeleteFilters"); + if (ctx != null && filter != 0) { + deleteFilterCheck(ctx, filter); + } + org.lwjgl.openal.EXTEfx.alDeleteFilters(filter); + } + + public static void alDeleteFilters(IntBuffer filters) { + ALContext ctx = ALRT.checkContext("alDeleteFilters"); + if (ctx != null) { + int pos = filters.position(); + for (int i = pos; i < filters.limit(); i++) { + int f = filters.get(i); + if (f != 0) { + deleteFilterCheck(ctx, f); + } + } + } + org.lwjgl.openal.EXTEfx.alDeleteFilters(filters); + } + + public static void alDeleteFilters(int[] filters) { + ALContext ctx = ALRT.checkContext("alDeleteFilters"); + if (ctx != null) { + for (int f : filters) { + if (f != 0) { + deleteFilterCheck(ctx, f); + } + } + } + org.lwjgl.openal.EXTEfx.alDeleteFilters(filters); + } + + private static void deleteFilterCheck(ALContext ctx, int filter) { + ALObjects.Filter f = ctx.filters.get(filter); + if (f == null) { + RT.throwISEOrLogError("alDeleteFilters: unknown filter " + filter); + return; + } + if (f.state == ResourceState.DELETED) { + RT.throwISEOrLogError("alDeleteFilters: filter " + filter + " already deleted (double-free)" + + ALRT.deletionInfo(f.deletionSite) + ALRT.creationInfo(f.creationSite)); + return; + } + f.state = ResourceState.DELETED; + if (Properties.STRICT.enabled) { + f.deletionSite = new Throwable("Filter deleted here"); + } + } + + public static boolean alIsFilter(int filter) { + ALRT.checkContext("alIsFilter"); + return org.lwjgl.openal.EXTEfx.alIsFilter(filter); + } + + public static void alFilteri(int filter, int param, int value) { + ALContext ctx = ALRT.checkContext("alFilteri"); + ALRT.checkFilter(ctx, filter, "alFilteri"); + org.lwjgl.openal.EXTEfx.alFilteri(filter, param, value); + } + + public static void alFilteriv(int filter, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alFilteriv"); + ALRT.checkFilter(ctx, filter, "alFilteriv"); + org.lwjgl.openal.EXTEfx.alFilteriv(filter, param, values); + } + + public static void alFilteriv(int filter, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alFilteriv"); + ALRT.checkFilter(ctx, filter, "alFilteriv"); + org.lwjgl.openal.EXTEfx.alFilteriv(filter, param, values); + } + + public static void alFilterf(int filter, int param, float value) { + ALContext ctx = ALRT.checkContext("alFilterf"); + ALRT.checkFilter(ctx, filter, "alFilterf"); + org.lwjgl.openal.EXTEfx.alFilterf(filter, param, value); + } + + public static void alFilterfv(int filter, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alFilterfv"); + ALRT.checkFilter(ctx, filter, "alFilterfv"); + org.lwjgl.openal.EXTEfx.alFilterfv(filter, param, values); + } + + public static void alFilterfv(int filter, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alFilterfv"); + ALRT.checkFilter(ctx, filter, "alFilterfv"); + org.lwjgl.openal.EXTEfx.alFilterfv(filter, param, values); + } + + public static int alGetFilteri(int filter, int param) { + ALContext ctx = ALRT.checkContext("alGetFilteri"); + ALRT.checkFilter(ctx, filter, "alGetFilteri"); + return org.lwjgl.openal.EXTEfx.alGetFilteri(filter, param); + } + + public static void alGetFilteri(int filter, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alGetFilteri"); + ALRT.checkFilter(ctx, filter, "alGetFilteri"); + org.lwjgl.openal.EXTEfx.alGetFilteri(filter, param, values); + } + + public static void alGetFilteri(int filter, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alGetFilteri"); + ALRT.checkFilter(ctx, filter, "alGetFilteri"); + org.lwjgl.openal.EXTEfx.alGetFilteri(filter, param, values); + } + + public static void alGetFilteriv(int filter, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alGetFilteriv"); + ALRT.checkFilter(ctx, filter, "alGetFilteriv"); + org.lwjgl.openal.EXTEfx.alGetFilteriv(filter, param, values); + } + + public static void alGetFilteriv(int filter, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alGetFilteriv"); + ALRT.checkFilter(ctx, filter, "alGetFilteriv"); + org.lwjgl.openal.EXTEfx.alGetFilteriv(filter, param, values); + } + + public static float alGetFilterf(int filter, int param) { + ALContext ctx = ALRT.checkContext("alGetFilterf"); + ALRT.checkFilter(ctx, filter, "alGetFilterf"); + return org.lwjgl.openal.EXTEfx.alGetFilterf(filter, param); + } + + public static void alGetFilterf(int filter, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alGetFilterf"); + ALRT.checkFilter(ctx, filter, "alGetFilterf"); + org.lwjgl.openal.EXTEfx.alGetFilterf(filter, param, values); + } + + public static void alGetFilterf(int filter, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alGetFilterf"); + ALRT.checkFilter(ctx, filter, "alGetFilterf"); + org.lwjgl.openal.EXTEfx.alGetFilterf(filter, param, values); + } + + public static void alGetFilterfv(int filter, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alGetFilterfv"); + ALRT.checkFilter(ctx, filter, "alGetFilterfv"); + org.lwjgl.openal.EXTEfx.alGetFilterfv(filter, param, values); + } + + public static void alGetFilterfv(int filter, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alGetFilterfv"); + ALRT.checkFilter(ctx, filter, "alGetFilterfv"); + org.lwjgl.openal.EXTEfx.alGetFilterfv(filter, param, values); + } + + /* --- Auxiliary Effect Slot Objects --- */ + + public static int alGenAuxiliaryEffectSlots() { + ALContext ctx = ALRT.checkContext("alGenAuxiliaryEffectSlots"); + int slot = org.lwjgl.openal.EXTEfx.alGenAuxiliaryEffectSlots(); + if (ctx != null && slot != 0) { + ctx.auxSlots.put(slot, new ALObjects.AuxiliaryEffectSlot(slot, ctx)); + } + return slot; + } + + public static void alGenAuxiliaryEffectSlots(IntBuffer slots) { + ALContext ctx = ALRT.checkContext("alGenAuxiliaryEffectSlots"); + int pos = slots.position(); + org.lwjgl.openal.EXTEfx.alGenAuxiliaryEffectSlots(slots); + if (ctx != null) { + for (int i = pos; i < slots.limit(); i++) { + int s = slots.get(i); + if (s != 0) { + ctx.auxSlots.put(s, new ALObjects.AuxiliaryEffectSlot(s, ctx)); + } + } + } + } + + public static void alGenAuxiliaryEffectSlots(int[] slots) { + ALContext ctx = ALRT.checkContext("alGenAuxiliaryEffectSlots"); + org.lwjgl.openal.EXTEfx.alGenAuxiliaryEffectSlots(slots); + if (ctx != null) { + for (int s : slots) { + if (s != 0) { + ctx.auxSlots.put(s, new ALObjects.AuxiliaryEffectSlot(s, ctx)); + } + } + } + } + + public static void alDeleteAuxiliaryEffectSlots(int slot) { + ALContext ctx = ALRT.checkContext("alDeleteAuxiliaryEffectSlots"); + if (ctx != null && slot != 0) { + deleteAuxSlotCheck(ctx, slot); + } + org.lwjgl.openal.EXTEfx.alDeleteAuxiliaryEffectSlots(slot); + } + + public static void alDeleteAuxiliaryEffectSlots(IntBuffer slots) { + ALContext ctx = ALRT.checkContext("alDeleteAuxiliaryEffectSlots"); + if (ctx != null) { + int pos = slots.position(); + for (int i = pos; i < slots.limit(); i++) { + int s = slots.get(i); + if (s != 0) { + deleteAuxSlotCheck(ctx, s); + } + } + } + org.lwjgl.openal.EXTEfx.alDeleteAuxiliaryEffectSlots(slots); + } + + public static void alDeleteAuxiliaryEffectSlots(int[] slots) { + ALContext ctx = ALRT.checkContext("alDeleteAuxiliaryEffectSlots"); + if (ctx != null) { + for (int s : slots) { + if (s != 0) { + deleteAuxSlotCheck(ctx, s); + } + } + } + org.lwjgl.openal.EXTEfx.alDeleteAuxiliaryEffectSlots(slots); + } + + private static void deleteAuxSlotCheck(ALContext ctx, int slot) { + ALObjects.AuxiliaryEffectSlot a = ctx.auxSlots.get(slot); + if (a == null) { + RT.throwISEOrLogError("alDeleteAuxiliaryEffectSlots: unknown auxiliary effect slot " + slot); + return; + } + if (a.state == ResourceState.DELETED) { + RT.throwISEOrLogError("alDeleteAuxiliaryEffectSlots: auxiliary effect slot " + slot + + " already deleted (double-free)" + + ALRT.deletionInfo(a.deletionSite) + ALRT.creationInfo(a.creationSite)); + return; + } + a.state = ResourceState.DELETED; + if (Properties.STRICT.enabled) { + a.deletionSite = new Throwable("Auxiliary effect slot deleted here"); + } + } + + public static boolean alIsAuxiliaryEffectSlot(int slot) { + ALRT.checkContext("alIsAuxiliaryEffectSlot"); + return org.lwjgl.openal.EXTEfx.alIsAuxiliaryEffectSlot(slot); + } + + public static void alAuxiliaryEffectSloti(int slot, int param, int value) { + ALContext ctx = ALRT.checkContext("alAuxiliaryEffectSloti"); + ALRT.checkAuxSlot(ctx, slot, "alAuxiliaryEffectSloti"); + if (param == org.lwjgl.openal.EXTEfx.AL_EFFECTSLOT_EFFECT && value != 0) { + ALRT.checkEffect(ctx, value, "alAuxiliaryEffectSloti(AL_EFFECTSLOT_EFFECT)"); + } + org.lwjgl.openal.EXTEfx.alAuxiliaryEffectSloti(slot, param, value); + } + + public static void alAuxiliaryEffectSlotiv(int slot, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alAuxiliaryEffectSlotiv"); + ALRT.checkAuxSlot(ctx, slot, "alAuxiliaryEffectSlotiv"); + org.lwjgl.openal.EXTEfx.alAuxiliaryEffectSlotiv(slot, param, values); + } + + public static void alAuxiliaryEffectSlotiv(int slot, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alAuxiliaryEffectSlotiv"); + ALRT.checkAuxSlot(ctx, slot, "alAuxiliaryEffectSlotiv"); + org.lwjgl.openal.EXTEfx.alAuxiliaryEffectSlotiv(slot, param, values); + } + + public static void alAuxiliaryEffectSlotf(int slot, int param, float value) { + ALContext ctx = ALRT.checkContext("alAuxiliaryEffectSlotf"); + ALRT.checkAuxSlot(ctx, slot, "alAuxiliaryEffectSlotf"); + org.lwjgl.openal.EXTEfx.alAuxiliaryEffectSlotf(slot, param, value); + } + + public static void alAuxiliaryEffectSlotfv(int slot, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alAuxiliaryEffectSlotfv"); + ALRT.checkAuxSlot(ctx, slot, "alAuxiliaryEffectSlotfv"); + org.lwjgl.openal.EXTEfx.alAuxiliaryEffectSlotfv(slot, param, values); + } + + public static void alAuxiliaryEffectSlotfv(int slot, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alAuxiliaryEffectSlotfv"); + ALRT.checkAuxSlot(ctx, slot, "alAuxiliaryEffectSlotfv"); + org.lwjgl.openal.EXTEfx.alAuxiliaryEffectSlotfv(slot, param, values); + } + + public static int alGetAuxiliaryEffectSloti(int slot, int param) { + ALContext ctx = ALRT.checkContext("alGetAuxiliaryEffectSloti"); + ALRT.checkAuxSlot(ctx, slot, "alGetAuxiliaryEffectSloti"); + return org.lwjgl.openal.EXTEfx.alGetAuxiliaryEffectSloti(slot, param); + } + + public static void alGetAuxiliaryEffectSloti(int slot, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alGetAuxiliaryEffectSloti"); + ALRT.checkAuxSlot(ctx, slot, "alGetAuxiliaryEffectSloti"); + org.lwjgl.openal.EXTEfx.alGetAuxiliaryEffectSloti(slot, param, values); + } + + public static void alGetAuxiliaryEffectSloti(int slot, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alGetAuxiliaryEffectSloti"); + ALRT.checkAuxSlot(ctx, slot, "alGetAuxiliaryEffectSloti"); + org.lwjgl.openal.EXTEfx.alGetAuxiliaryEffectSloti(slot, param, values); + } + + public static void alGetAuxiliaryEffectSlotiv(int slot, int param, IntBuffer values) { + ALContext ctx = ALRT.checkContext("alGetAuxiliaryEffectSlotiv"); + ALRT.checkAuxSlot(ctx, slot, "alGetAuxiliaryEffectSlotiv"); + org.lwjgl.openal.EXTEfx.alGetAuxiliaryEffectSlotiv(slot, param, values); + } + + public static void alGetAuxiliaryEffectSlotiv(int slot, int param, int[] values) { + ALContext ctx = ALRT.checkContext("alGetAuxiliaryEffectSlotiv"); + ALRT.checkAuxSlot(ctx, slot, "alGetAuxiliaryEffectSlotiv"); + org.lwjgl.openal.EXTEfx.alGetAuxiliaryEffectSlotiv(slot, param, values); + } + + public static float alGetAuxiliaryEffectSlotf(int slot, int param) { + ALContext ctx = ALRT.checkContext("alGetAuxiliaryEffectSlotf"); + ALRT.checkAuxSlot(ctx, slot, "alGetAuxiliaryEffectSlotf"); + return org.lwjgl.openal.EXTEfx.alGetAuxiliaryEffectSlotf(slot, param); + } + + public static void alGetAuxiliaryEffectSlotf(int slot, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alGetAuxiliaryEffectSlotf"); + ALRT.checkAuxSlot(ctx, slot, "alGetAuxiliaryEffectSlotf"); + org.lwjgl.openal.EXTEfx.alGetAuxiliaryEffectSlotf(slot, param, values); + } + + public static void alGetAuxiliaryEffectSlotf(int slot, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alGetAuxiliaryEffectSlotf"); + ALRT.checkAuxSlot(ctx, slot, "alGetAuxiliaryEffectSlotf"); + org.lwjgl.openal.EXTEfx.alGetAuxiliaryEffectSlotf(slot, param, values); + } + + public static void alGetAuxiliaryEffectSlotfv(int slot, int param, FloatBuffer values) { + ALContext ctx = ALRT.checkContext("alGetAuxiliaryEffectSlotfv"); + ALRT.checkAuxSlot(ctx, slot, "alGetAuxiliaryEffectSlotfv"); + org.lwjgl.openal.EXTEfx.alGetAuxiliaryEffectSlotfv(slot, param, values); + } + + public static void alGetAuxiliaryEffectSlotfv(int slot, int param, float[] values) { + ALContext ctx = ALRT.checkContext("alGetAuxiliaryEffectSlotfv"); + ALRT.checkAuxSlot(ctx, slot, "alGetAuxiliaryEffectSlotfv"); + org.lwjgl.openal.EXTEfx.alGetAuxiliaryEffectSlotfv(slot, param, values); + } +} diff --git a/test/test/DebugALIT.java b/test/test/DebugALIT.java new file mode 100644 index 0000000..770e180 --- /dev/null +++ b/test/test/DebugALIT.java @@ -0,0 +1,289 @@ +package test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; +import static org.lwjgl.openal.AL10.*; +import static org.lwjgl.openal.ALC10.*; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lwjgl.BufferUtils; +import org.lwjgl.openal.AL; +import org.lwjgl.openal.ALC; +import org.lwjgl.openal.ALCCapabilities; +import org.lwjgl.openal.ALCapabilities; +import org.lwjgl.openal.EXTEfx; + +public class DebugALIT { + + private long device; + private long context; + + @BeforeEach + public void setUp() { + device = alcOpenDevice((ByteBuffer) null); + assumeTrue(device != 0L, "OpenAL device not available"); + context = alcCreateContext(device, (IntBuffer) null); + assumeTrue(context != 0L, "OpenAL context creation failed"); + alcMakeContextCurrent(context); + ALCCapabilities alcCaps = ALC.createCapabilities(device); + AL.createCapabilities(alcCaps); + } + + @AfterEach + public void tearDown() { + if (context != 0L) { + alcMakeContextCurrent(0L); + alcDestroyContext(context); + context = 0L; + } + if (device != 0L) { + alcCloseDevice(device); + device = 0L; + } + } + + @Test + public void testNoContextCurrent() { + alcMakeContextCurrent(0L); + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alGenBuffers()); + assertTrue(ex.getMessage().contains("No OpenAL context is current")); + } + + @Test + public void testBufferLifecycleAndDoubleFree() { + int buf = alGenBuffers(); + assertTrue(buf > 0); + assertTrue(alIsBuffer(buf)); + + alDeleteBuffers(buf); + + // Double-free + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alDeleteBuffers(buf)); + assertTrue(ex.getMessage().contains("already deleted (double-free)")); + + // Use-after-free + ByteBuffer data = BufferUtils.createByteBuffer(16); + IllegalStateException ex2 = assertThrows(IllegalStateException.class, + () -> alBufferData(buf, AL_FORMAT_MONO16, data, 44100)); + assertTrue(ex2.getMessage().contains("has been deleted (use-after-free)")); + } + + @Test + public void testSourceLifecycleAndDoubleFree() { + int src = alGenSources(); + assertTrue(src > 0); + assertTrue(alIsSource(src)); + + alDeleteSources(src); + + // Double-free + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alDeleteSources(src)); + assertTrue(ex.getMessage().contains("already deleted (double-free)")); + + // Use-after-free + IllegalStateException ex2 = assertThrows(IllegalStateException.class, () -> alSourcePlay(src)); + assertTrue(ex2.getMessage().contains("has been deleted (use-after-free)")); + } + + @Test + public void testBufferAttachedToSourceDeleteWarning() { + int buf = alGenBuffers(); + int src = alGenSources(); + + alSourcei(src, AL_BUFFER, buf); + + // Deleting buffer while still attached + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alDeleteBuffers(buf)); + assertTrue(ex.getMessage().contains("is still attached to 1 source(s)")); + + // Detach buffer + alSourcei(src, AL_BUFFER, 0); + + // Now deletion should succeed + alDeleteBuffers(buf); + + // Clean up source + alDeleteSources(src); + } + + @Test + public void testBufferDataOnAttachedBuffer() { + int buf = alGenBuffers(); + int src = alGenSources(); + + alSourcei(src, AL_BUFFER, buf); + + ByteBuffer data = BufferUtils.createByteBuffer(16); + IllegalStateException ex = assertThrows(IllegalStateException.class, + () -> alBufferData(buf, AL_FORMAT_MONO16, data, 44100)); + assertTrue(ex.getMessage().contains("while attached to source(s) is undefined behavior")); + + alSourcei(src, AL_BUFFER, 0); + alDeleteBuffers(buf); + alDeleteSources(src); + } + + @Test + public void testContextDestructionDoubleFree() { + long tempCtx = alcCreateContext(device, (IntBuffer) null); + assertTrue(tempCtx != 0L); + + alcDestroyContext(tempCtx); + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alcDestroyContext(tempCtx)); + assertTrue(ex.getMessage().contains("already been destroyed (double-free)")); + } + + @Test + public void testMakeDestroyedContextCurrent() { + long tempCtx = alcCreateContext(device, (IntBuffer) null); + assertTrue(tempCtx != 0L); + + alcDestroyContext(tempCtx); + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alcMakeContextCurrent(tempCtx)); + assertTrue(ex.getMessage().contains("has already been destroyed (use-after-free)")); + } + + @Test + public void testContextLeakedResourcesAudit() { + long tempCtx = alcCreateContext(device, (IntBuffer) null); + assertTrue(tempCtx != 0L); + alcMakeContextCurrent(tempCtx); + + // Leak a buffer and a source + alGenBuffers(); + alGenSources(); + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alcDestroyContext(tempCtx)); + assertTrue(ex.getMessage().contains("leaked")); + } + + @Test + public void testCloseDeviceWithLiveContext() { + long tempDev = alcOpenDevice((ByteBuffer) null); + assumeTrue(tempDev != 0L); + long tempCtx = alcCreateContext(tempDev, (IntBuffer) null); + assumeTrue(tempCtx != 0L); + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alcCloseDevice(tempDev)); + assertTrue(ex.getMessage().contains("is still alive")); + + alcDestroyContext(tempCtx); + alcCloseDevice(tempDev); + } + + @Test + public void testDeviceUseAfterFree() { + long tempDev = alcOpenDevice((ByteBuffer) null); + assumeTrue(tempDev != 0L); + alcCloseDevice(tempDev); + + IllegalStateException ex = assertThrows(IllegalStateException.class, + () -> alcCreateContext(tempDev, (IntBuffer) null)); + assertTrue(ex.getMessage().contains("device has been closed")); + } + + @Test + public void testEXTEfxValidation() { + ALCapabilities caps = AL.getCapabilities(); + assumeTrue(caps.ALC_EXT_EFX, "ALC_EXT_EFX extension not supported"); + + int effect = EXTEfx.alGenEffects(); + assertTrue(effect > 0); + assertTrue(EXTEfx.alIsEffect(effect)); + + EXTEfx.alDeleteEffects(effect); + + // Double-free + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> EXTEfx.alDeleteEffects(effect)); + assertTrue(ex.getMessage().contains("already deleted (double-free)")); + + // Use-after-free + IllegalStateException ex2 = assertThrows(IllegalStateException.class, + () -> EXTEfx.alEffecti(effect, EXTEfx.AL_EFFECT_TYPE, EXTEfx.AL_EFFECT_REVERB)); + assertTrue(ex2.getMessage().contains("has been deleted (use-after-free)")); + + // Filter + int filter = EXTEfx.alGenFilters(); + assertTrue(filter > 0); + EXTEfx.alDeleteFilters(filter); + + IllegalStateException ex3 = assertThrows(IllegalStateException.class, () -> EXTEfx.alDeleteFilters(filter)); + assertTrue(ex3.getMessage().contains("already deleted (double-free)")); + + // Aux Slot + int slot = EXTEfx.alGenAuxiliaryEffectSlots(); + assertTrue(slot > 0); + EXTEfx.alDeleteAuxiliaryEffectSlots(slot); + + IllegalStateException ex4 = assertThrows(IllegalStateException.class, + () -> EXTEfx.alDeleteAuxiliaryEffectSlots(slot)); + assertTrue(ex4.getMessage().contains("already deleted (double-free)")); + } + + @Test + public void testCrossContextObjectUsage() { + long ctx2 = alcCreateContext(device, (IntBuffer) null); + assertTrue(ctx2 != 0L); + + // Create buffer under current context (this.context) + int buf = alGenBuffers(); + assertTrue(buf > 0); + + // Switch to ctx2 + alcMakeContextCurrent(ctx2); + + // Try to use buffer from context 1 while context 2 is current + ByteBuffer data = BufferUtils.createByteBuffer(16); + IllegalStateException ex = assertThrows(IllegalStateException.class, + () -> alBufferData(buf, AL_FORMAT_MONO16, data, 44100)); + assertTrue(ex.getMessage().contains("cross-context use")); + + // Switch back to context 1, delete buffer, then destroy ctx2 + alcMakeContextCurrent(context); + alDeleteBuffers(buf); + + alcDestroyContext(ctx2); + } + + @Test + public void testAlcDestroyWithOpenDevice() { + long tempDev = alcOpenDevice((ByteBuffer) null); + assumeTrue(tempDev != 0L); + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> ALC.destroy()); + assertTrue(ex.getMessage().contains("was never closed")); + + alcCloseDevice(tempDev); + } + + @Test + public void testTraceWithOpenAL() { + org.lwjglx.debug.Properties.TRACE.enable(); + try { + int buf = alGenBuffers(); + assertTrue(buf > 0); + ByteBuffer data = BufferUtils.createByteBuffer(16); + alBufferData(buf, AL_FORMAT_MONO16, data, 44100); + int freq = alGetBufferi(buf, AL_FREQUENCY); + assertEquals(44100, freq); + alDeleteBuffers(buf); + } finally { + org.lwjglx.debug.Properties.TRACE.enabled = false; + } + } + + @Test + public void testAlMetadataEnumNaming() { + assertEquals("AL_NO_ERROR", org.lwjglx.debug.openal.ALMetadata.enumName(0)); + assertEquals("AL_NO_ERROR", org.lwjglx.debug.openal.ALMetadata.alErrorName(0)); + assertEquals("AL_INVALID_NAME", org.lwjglx.debug.openal.ALMetadata.enumName(0xA001)); + assertEquals("AL_INVALID_NAME", org.lwjglx.debug.openal.ALMetadata.alErrorName(0xA001)); + } +} From c0ff3caa20a110299734c6cbd77bb5788251e801 Mon Sep 17 00:00:00 2001 From: Mike Duigou Date: Wed, 30 Sep 2026 14:28:09 -0700 Subject: [PATCH 2/3] Add OpenGL resource lifecycle tracking --- README.md | 50 ++- src/org/lwjglx/debug/Agent.java | 6 + src/org/lwjglx/debug/Log.java | 6 +- src/org/lwjglx/debug/Properties.java | 1 + .../debug/{openal => }/ResourceState.java | 2 +- src/org/lwjglx/debug/ResourceTracker.java | 128 +++++++ src/org/lwjglx/debug/openal/ALContext.java | 1 + src/org/lwjglx/debug/openal/ALDevice.java | 1 + src/org/lwjglx/debug/openal/ALObjects.java | 1 + src/org/lwjglx/debug/openal/ALRT.java | 1 + .../lwjglx/debug/org/lwjgl/openal/AL10.java | 2 +- .../lwjglx/debug/org/lwjgl/openal/ALC.java | 2 +- .../lwjglx/debug/org/lwjgl/openal/ALC10.java | 30 +- .../lwjglx/debug/org/lwjgl/openal/ALC11.java | 2 +- .../lwjglx/debug/org/lwjgl/openal/EXTEfx.java | 2 +- .../lwjgl/opengl/ARBDirectStateAccess.java | 264 ++++++++++++++- .../lwjgl/opengl/ARBFramebufferObject.java | 215 ++++++++++-- .../lwjgl/opengl/ARBTextureBufferObject.java | 15 + .../lwjgl/opengl/ARBTextureBufferRange.java | 28 ++ .../lwjgl/opengl/ARBVertexArrayObject.java | 12 +- .../debug/org/lwjgl/opengl/Context.java | 271 ++++++++++++--- .../lwjgl/opengl/EXTFramebufferObject.java | 32 +- .../lwjglx/debug/org/lwjgl/opengl/GL11.java | 91 ++++- .../lwjglx/debug/org/lwjgl/opengl/GL15.java | 152 +++++++++ .../lwjglx/debug/org/lwjgl/opengl/GL20.java | 134 ++++++++ .../lwjglx/debug/org/lwjgl/opengl/GL30.java | 243 +++++++++++-- .../lwjglx/debug/org/lwjgl/opengl/GL31.java | 8 + .../lwjglx/debug/org/lwjgl/opengl/GL43.java | 15 + .../lwjglx/debug/org/lwjgl/opengl/GL45.java | 225 +++++++++++- test/test/DebugALIT.java | 19 +- test/test/DebugIT.java | 320 +++++++++++++++++- 31 files changed, 2099 insertions(+), 180 deletions(-) rename src/org/lwjglx/debug/{openal => }/ResourceState.java (59%) create mode 100644 src/org/lwjglx/debug/ResourceTracker.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/opengl/ARBTextureBufferObject.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/opengl/ARBTextureBufferRange.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/opengl/GL15.java create mode 100644 src/org/lwjglx/debug/org/lwjgl/opengl/GL43.java diff --git a/README.md b/README.md index 59dcd0b..3af9bdd 100644 --- a/README.md +++ b/README.md @@ -2,40 +2,60 @@ # What -Java Agent for debugging LWJGL3 programs to prevent JVM crashes and resolve OpenGL errors. +Java Agent for debugging LWJGL3 programs to prevent JVM crashes, resolve OpenGL and OpenAL errors, and detect resource leaks. # Why Because some errors in user programs can cause the JVM to crash without a meaningful error message, since LWJGL 3 is tuned for extreme speed at the expense of robustness. +# Capabilities + +### OpenGL Validation & Lifecycle Tracking +- **API Coverage:** OpenGL 1.1 through 4.6 (including Direct State Access / DSA), ARB, and EXT extensions. +- **Resource Lifecycle Management:** Tracks the lifecycle of Textures, Buffer Objects, Shaders, Programs, Framebuffers (FBOs), Renderbuffers (RBOs), and Vertex Array Objects (VAOs). +- **Use-After-Free & Double-Free Protection:** Immediate detection and detailed reporting when referencing or re-deleting destroyed resource handles. +- **Cross-Resource Reference Tracking:** Audits attachments between dependent objects (e.g. shaders attached to programs, textures/renderbuffers attached to framebuffers) and warns if an attached resource is deleted. +- **Teardown Leak Auditing:** Audits remaining unreleased resources when a context is destroyed. Warns by default, or optionally throws an exception. + +### OpenAL Validation & Lifecycle Tracking +- **API Coverage:** OpenAL 1.0, 1.1, ALC 1.0, 1.1, and EXT_EFX (Effects, Filters, Auxiliary Effect Slots). +- **Context & Device Validation:** Validates current context and valid device states on every AL and ALC invocation. +- **Resource Lifecycle Management:** Tracks Sources, Buffers, Effects, Filters, and Auxiliary Effect Slots. +- **Attachment & Dependency Checking:** Prevents use-after-free and detects dangling dependencies, such as deleting a buffer currently attached to an active source. +- **Automatic Error Injection:** Automatically invokes `alGetError()` and `alcGetError()` to catch and report errors immediately at the site of failure. +- **Teardown Leak Auditing:** Audits remaining unreleased resources when an AL context or device is closed. + # How -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 ``) -3. Start your LWJGL3 application with the **JVM/VM argument** (_not_ program argument) `-javaagent:/lwjglx-debug-1.0.4.jar` - 1. when using the command line, it should look like: `java -javaagent:/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:/lwjglx-debug-1.0.0.jar` +1. Download the `lwjglx-debug-1.1.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.1.0.jar` to any directory (henceforth called ``) +3. Start your LWJGL3 application with the **JVM/VM argument** (_not_ program argument) `-javaagent:/lwjglx-debug-1.1.0.jar` + 1. when using the command line, it should look like: `java -javaagent:/lwjglx-debug-1.1.0.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:/lwjglx-debug-1.1.0.jar` # Configuration The following configuration properties are available to configure the library: -- `validate` - Perform argument validation and check for GL errors on each GL call (enabled by default, set via system property `-Dorg.lwjglx.VALIDATE` or via Agent argument `v`) +- `validate` - Perform argument validation and check for errors on each GL/AL call (enabled by default, set via system property `-Dorg.lwjglx.VALIDATE` or via Agent argument `v` / `validate`) +- `strict` - Strict validation mode; captures allocation and deletion stack traces for all tracked resources to provide detailed diagnostics on use-after-free, double-free, and leak detection (disabled by default, set via system property `-Dorg.lwjglx.STRICT` or via Agent argument `strict` or `validate=s`) +- `failonleaks` - Fail on leaks; throws an `IllegalStateException` during context teardown if any resources were leaked, instead of only logging warnings (disabled by default, set via system property `-Dorg.lwjglx.FAIL_ON_LEAKS` or via Agent argument `failonleaks`) - `trace` - Generate a trace log (set via system property `-Dorg.lwjglx.TRACE` or via Agent argument `t`) - `exclude` - Exclude trace outputs for called methods matching a given GLOB pattern (set via Agent argument `e`) -- `nothrow` - Do not throw a Java exception on any detected error but only log the error. Note that this may result in a JVM crash due to illegal arguments or GL errors. (set via system property `-Dorg.lwjglx.NO_THROW` or via Agent argument `n`) -- `debug` - Log additional information about classfile transformations (this can be used to debug the library itself). (set via system property `org.lwjglx.DEBUG` or via Agent argument `d`) +- `nothrow` - Do not throw a Java exception on any detected error but only log the error. Note that this may result in a JVM crash due to illegal arguments or native errors. (set via system property `-Dorg.lwjglx.NO_THROW` or via Agent argument `n`) +- `debug` - Log additional information about classfile transformations (this can be used to debug the library itself). (set via system property `-Dorg.lwjglx.DEBUG` or via Agent argument `d`) - `output` - Write LWJGL3 and LWJGLX debug and trace logging messages to a file (when this option is set, no output of LWJGL3 and LWJGLX is printed to stdout or stderr, but instead to the specified file). The file name is the value of this property. When the file name ends with `.zip` or `.gz` then a corresponding compressed archive file will be created to save storage space. In this case, the JVM must exit normally for the archive file to be finalized properly. (set via system property `-Dorg.lwjglx.OUTPUT` or via Agent argument `o`) - `sleep` - Thread.sleep() before calling each intercepted method (useful when following a call trace). The number of milliseconds are specified as the value of this property. (set via system property `-Dorg.lwjglx.SLEEP` or via Agent argument `s`) Examples: -* `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`) +* `java -javaagent:lwjglx-debug-1.1.0.jar=t ...` (generate a trace on stderr) +* `java -javaagent:lwjglx-debug-1.1.0.jar=strict;failonleaks ...` (strict mode with allocation stack traces and fail on leaks) +* `java -javaagent:lwjglx-debug-1.1.0.jar=t;o=trace.log` (generate a trace written to file `trace.log`) +* `java -javaagent:lwjglx-debug-1.1.0.jar=t;o=trace.log.zip` (generate a zip archive containing a single `trace.log` file) +* `java -javaagent:lwjglx-debug-1.1.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.1.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`) # Build 1. `./mvnw package` -2. see target/lwjglx-debug-1.0.4.jar +2. see target/lwjglx-debug-1.1.0.jar diff --git a/src/org/lwjglx/debug/Agent.java b/src/org/lwjglx/debug/Agent.java index 5883ec6..ca54b86 100644 --- a/src/org/lwjglx/debug/Agent.java +++ b/src/org/lwjglx/debug/Agent.java @@ -402,6 +402,8 @@ public static void premain(String agentArguments, Instrumentation instrumentatio parser.accepts("debug"); parser.accepts("trace"); parser.accepts("nothrow"); + parser.accepts("failonleaks"); + parser.accepts("strict"); OptionSpec validate = parser.accepts("validate").withOptionalArg().ofType(String.class); OptionSpec sleep = parser.accepts("sleep").withRequiredArg().ofType(Long.class); OptionSpec output = parser.accepts("output").withRequiredArg().ofType(String.class); @@ -416,6 +418,10 @@ public static void premain(String agentArguments, Instrumentation instrumentatio Properties.DEBUG.enable(); if (options.has("trace")) Properties.TRACE.enable(); + if (options.has("strict")) + Properties.STRICT.enable(); + if (options.has("failonleaks")) + Properties.FAIL_ON_LEAKS.enable(); if (options.has("validate")) { Properties.VALIDATE.enable(); String validateArgsString = options.valueOf(validate); diff --git a/src/org/lwjglx/debug/Log.java b/src/org/lwjglx/debug/Log.java index bb9b219..3016dcb 100644 --- a/src/org/lwjglx/debug/Log.java +++ b/src/org/lwjglx/debug/Log.java @@ -198,9 +198,13 @@ public static void error(String message, Throwable t) { log("error", message, t); } + public static void warn(String message) { + log("warn ", message); + } + public static void warn(String message, Throwable t, int offset) { t = RT.filterStackTrace(t, offset); - log("warn", message, t); + log("warn ", message, t); } public static void trace(String message) { diff --git a/src/org/lwjglx/debug/Properties.java b/src/org/lwjglx/debug/Properties.java index 19ad185..3b40950 100644 --- a/src/org/lwjglx/debug/Properties.java +++ b/src/org/lwjglx/debug/Properties.java @@ -46,6 +46,7 @@ public void disableIfByDefault() { public static final BooleanProperty DEBUG = getBooleanProperty("org.lwjglx.DEBUG", false); public static final BooleanProperty TRACE = getBooleanProperty("org.lwjglx.TRACE", false); public static final BooleanProperty NO_THROW_ON_ERROR = getBooleanProperty("org.lwjglx.NO_THROW", false); + public static final BooleanProperty FAIL_ON_LEAKS = getBooleanProperty("org.lwjglx.FAIL_ON_LEAKS", false); public static String OUTPUT = System.getProperty("org.lwjglx.OUTPUT", null); public static long SLEEP = getLongProperty("org.lwjglx.SLEEP", 0L); diff --git a/src/org/lwjglx/debug/openal/ResourceState.java b/src/org/lwjglx/debug/ResourceState.java similarity index 59% rename from src/org/lwjglx/debug/openal/ResourceState.java rename to src/org/lwjglx/debug/ResourceState.java index b5d3dca..8385382 100644 --- a/src/org/lwjglx/debug/openal/ResourceState.java +++ b/src/org/lwjglx/debug/ResourceState.java @@ -1,4 +1,4 @@ -package org.lwjglx.debug.openal; +package org.lwjglx.debug; public enum ResourceState { ALIVE, diff --git a/src/org/lwjglx/debug/ResourceTracker.java b/src/org/lwjglx/debug/ResourceTracker.java new file mode 100644 index 0000000..ff1452e --- /dev/null +++ b/src/org/lwjglx/debug/ResourceTracker.java @@ -0,0 +1,128 @@ +package org.lwjglx.debug; + +import java.util.concurrent.ConcurrentHashMap; + +public class ResourceTracker { + + public static class Entry { + public final int handle; + public final T object; + public volatile ResourceState state = ResourceState.ALIVE; + public final Throwable creationSite; + public volatile Throwable deletionSite; + + public Entry(int handle, T object) { + this.handle = handle; + this.object = object; + this.creationSite = Properties.STRICT.enabled ? new Throwable("Resource [" + handle + "] created here") : null; + } + } + + private final String resourceTypeName; + private final ConcurrentHashMap> entries = new ConcurrentHashMap<>(); + + public ResourceTracker(String resourceTypeName) { + this.resourceTypeName = resourceTypeName; + } + + public Entry create(int handle, T object) { + Entry entry = new Entry<>(handle, object); + entries.put(handle, entry); + return entry; + } + + public Entry get(int handle) { + return entries.get(handle); + } + + public Entry delete(int handle, String callerMethod) { + Entry entry = entries.get(handle); + if (entry == null) { + RT.throwISEOrLogError(callerMethod + ": unknown " + resourceTypeName + " handle " + handle); + return null; + } + if (entry.state == ResourceState.DELETED) { + RT.throwISEOrLogError(callerMethod + ": " + resourceTypeName + " " + handle + " already deleted (double-free)" + + deletionInfo(entry.deletionSite) + creationInfo(entry.creationSite)); + return null; + } + entry.state = ResourceState.DELETED; + if (Properties.STRICT.enabled) { + entry.deletionSite = new Throwable("Resource [" + handle + "] deleted here"); + } + return entry; + } + + public Entry checkAlive(int handle, String callerMethod) { + if (handle == 0) return null; + Entry entry = entries.get(handle); + if (entry == null) { + RT.throwISEOrLogError(callerMethod + ": unknown " + resourceTypeName + " handle " + handle); + return null; + } + if (entry.state == ResourceState.DELETED) { + RT.throwISEOrLogError(callerMethod + ": " + resourceTypeName + " " + handle + " has been deleted (use-after-free)" + + deletionInfo(entry.deletionSite) + creationInfo(entry.creationSite)); + return null; + } + return entry; + } + + public int liveCount() { + int count = 0; + for (Entry entry : entries.values()) { + if (entry.state == ResourceState.ALIVE) { + count++; + } + } + return count; + } + + public int liveCountExcludingDefault() { + int count = 0; + for (Entry entry : entries.values()) { + if (entry.handle != 0 && entry.state == ResourceState.ALIVE) { + count++; + } + } + return count; + } + + public java.util.List> liveEntriesExcludingDefault() { + java.util.List> result = new java.util.ArrayList<>(); + for (Entry entry : entries.values()) { + if (entry.handle != 0 && entry.state == ResourceState.ALIVE) { + result.add(entry); + } + } + return result; + } + + public ConcurrentHashMap> entries() { + return entries; + } + + private static String deletionInfo(Throwable deletionSite) { + if (deletionSite != null) { + return "\n Deleted at: " + formatTrace(deletionSite); + } + return ""; + } + + private static String creationInfo(Throwable creationSite) { + if (creationSite != null) { + return "\n Created at: " + formatTrace(creationSite); + } + return ""; + } + + public static String formatTrace(Throwable t) { + StackTraceElement[] stack = t.getStackTrace(); + for (StackTraceElement ste : stack) { + if (!ste.getClassName().startsWith("org.lwjglx.debug")) { + return ste.toString(); + } + } + return stack.length > 0 ? stack[0].toString() : "(unknown)"; + } +} diff --git a/src/org/lwjglx/debug/openal/ALContext.java b/src/org/lwjglx/debug/openal/ALContext.java index 46ee8cf..7322b9f 100644 --- a/src/org/lwjglx/debug/openal/ALContext.java +++ b/src/org/lwjglx/debug/openal/ALContext.java @@ -3,6 +3,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.lwjglx.debug.Properties; +import org.lwjglx.debug.ResourceState; public class ALContext { diff --git a/src/org/lwjglx/debug/openal/ALDevice.java b/src/org/lwjglx/debug/openal/ALDevice.java index 1fde04c..61b6301 100644 --- a/src/org/lwjglx/debug/openal/ALDevice.java +++ b/src/org/lwjglx/debug/openal/ALDevice.java @@ -4,6 +4,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.lwjglx.debug.Properties; +import org.lwjglx.debug.ResourceState; public class ALDevice { diff --git a/src/org/lwjglx/debug/openal/ALObjects.java b/src/org/lwjglx/debug/openal/ALObjects.java index c608a5f..ad1f29a 100644 --- a/src/org/lwjglx/debug/openal/ALObjects.java +++ b/src/org/lwjglx/debug/openal/ALObjects.java @@ -4,6 +4,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.lwjglx.debug.Properties; +import org.lwjglx.debug.ResourceState; public class ALObjects { diff --git a/src/org/lwjglx/debug/openal/ALRT.java b/src/org/lwjglx/debug/openal/ALRT.java index 4560b00..e756f4b 100644 --- a/src/org/lwjglx/debug/openal/ALRT.java +++ b/src/org/lwjglx/debug/openal/ALRT.java @@ -4,6 +4,7 @@ import org.lwjglx.debug.Properties; import org.lwjglx.debug.RT; +import org.lwjglx.debug.ResourceState; /** * OpenAL runtime validation helpers. diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java b/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java index df485a5..1dc417c 100644 --- a/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java +++ b/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java @@ -11,7 +11,7 @@ import org.lwjglx.debug.openal.ALContext; import org.lwjglx.debug.openal.ALObjects; import org.lwjglx.debug.openal.ALRT; -import org.lwjglx.debug.openal.ResourceState; +import org.lwjglx.debug.ResourceState; public class AL10 { diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/ALC.java b/src/org/lwjglx/debug/org/lwjgl/openal/ALC.java index f20e5f0..c02250a 100644 --- a/src/org/lwjglx/debug/org/lwjgl/openal/ALC.java +++ b/src/org/lwjglx/debug/org/lwjgl/openal/ALC.java @@ -23,7 +23,7 @@ public static ALCCapabilities createCapabilities(long device, IntFunction 0) { - RT.throwISEOrLogError("alcDestroyContext: leaked " + liveBuffers + " buffer(s) in context 0x" - + Long.toHexString(context)); + hasLeaks = true; + Log.warn("OpenAL context 0x" + Long.toHexString(context) + " destroyed with " + liveBuffers + " un-deleted buffer(s)"); } int liveSources = 0; @@ -140,8 +142,8 @@ public static void alcDestroyContext(long context) { } } if (liveSources > 0) { - RT.throwISEOrLogError("alcDestroyContext: leaked " + liveSources + " source(s) in context 0x" - + Long.toHexString(context)); + hasLeaks = true; + Log.warn("OpenAL context 0x" + Long.toHexString(context) + " destroyed with " + liveSources + " un-deleted source(s)"); } int liveEffects = 0; @@ -151,8 +153,8 @@ public static void alcDestroyContext(long context) { } } if (liveEffects > 0) { - RT.throwISEOrLogError("alcDestroyContext: leaked " + liveEffects + " effect(s) in context 0x" - + Long.toHexString(context)); + hasLeaks = true; + Log.warn("OpenAL context 0x" + Long.toHexString(context) + " destroyed with " + liveEffects + " un-deleted effect(s)"); } int liveFilters = 0; @@ -162,8 +164,8 @@ public static void alcDestroyContext(long context) { } } if (liveFilters > 0) { - RT.throwISEOrLogError("alcDestroyContext: leaked " + liveFilters + " filter(s) in context 0x" - + Long.toHexString(context)); + hasLeaks = true; + Log.warn("OpenAL context 0x" + Long.toHexString(context) + " destroyed with " + liveFilters + " un-deleted filter(s)"); } int liveAuxSlots = 0; @@ -173,8 +175,12 @@ public static void alcDestroyContext(long context) { } } if (liveAuxSlots > 0) { - RT.throwISEOrLogError("alcDestroyContext: leaked " + liveAuxSlots + " auxiliary effect slot(s) in context 0x" - + Long.toHexString(context)); + hasLeaks = true; + Log.warn("OpenAL context 0x" + Long.toHexString(context) + " destroyed with " + liveAuxSlots + " un-deleted auxiliary effect slot(s)"); + } + + if (hasLeaks && Properties.FAIL_ON_LEAKS.enabled) { + RT.throwISEOrLogError("OpenAL context 0x" + Long.toHexString(context) + " destroyed with leaked resources"); } } finally { org.lwjgl.openal.ALC10.alcDestroyContext(context); diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/ALC11.java b/src/org/lwjglx/debug/org/lwjgl/openal/ALC11.java index d07af74..753aae0 100644 --- a/src/org/lwjglx/debug/org/lwjgl/openal/ALC11.java +++ b/src/org/lwjglx/debug/org/lwjgl/openal/ALC11.java @@ -7,7 +7,7 @@ import org.lwjglx.debug.openal.ALDevice; import org.lwjglx.debug.openal.ALRT; -import org.lwjglx.debug.openal.ResourceState; +import org.lwjglx.debug.ResourceState; public class ALC11 { diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/EXTEfx.java b/src/org/lwjglx/debug/org/lwjgl/openal/EXTEfx.java index 01ce64c..afcac57 100644 --- a/src/org/lwjglx/debug/org/lwjgl/openal/EXTEfx.java +++ b/src/org/lwjglx/debug/org/lwjgl/openal/EXTEfx.java @@ -8,7 +8,7 @@ import org.lwjglx.debug.openal.ALContext; import org.lwjglx.debug.openal.ALObjects; import org.lwjglx.debug.openal.ALRT; -import org.lwjglx.debug.openal.ResourceState; +import org.lwjglx.debug.ResourceState; public class EXTEfx { diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/ARBDirectStateAccess.java b/src/org/lwjglx/debug/org/lwjgl/opengl/ARBDirectStateAccess.java index e6cc68e..8074f30 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/ARBDirectStateAccess.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/ARBDirectStateAccess.java @@ -25,6 +25,10 @@ of this software and associated documentation files (the "Software"), to deal import java.nio.IntBuffer; import org.lwjglx.debug.Properties; +import org.lwjglx.debug.org.lwjgl.opengl.Context.BufferObject; +import org.lwjglx.debug.org.lwjgl.opengl.Context.FBO; +import org.lwjglx.debug.org.lwjgl.opengl.Context.ProgramPipeline; +import org.lwjglx.debug.org.lwjgl.opengl.Context.TextureObject; import org.lwjglx.debug.org.lwjgl.opengl.Context.VAO; public class ARBDirectStateAccess { @@ -35,8 +39,10 @@ public static void glCreateVertexArrays(IntBuffer arrays) { Context context = Context.currentContext(); int position = arrays.position(); for (int i = 0; i < arrays.remaining(); i++) { + int handle = arrays.get(position + i); VAO vao = new VAO(context.GL_MAX_VERTEX_ATTRIBS); - context.vaos.put(arrays.get(position + i), vao); + context.vaos.put(handle, vao); + context.vaoTracker.create(handle, vao); } } } @@ -47,24 +53,272 @@ public static int glCreateVertexArrays() { Context context = Context.currentContext(); VAO vao = new VAO(context.GL_MAX_VERTEX_ATTRIBS); context.vaos.put(index, vao); + context.vaoTracker.create(index, vao); } return index; } public static void glDisableVertexArrayAttrib(int vaobj, int index) { - org.lwjgl.opengl.ARBDirectStateAccess.glDisableVertexArrayAttrib(vaobj, index); if (Properties.VALIDATE.enabled) { Context context = Context.currentContext(); - context.vaos.get(vaobj).enabledVertexArrays[index] = false; + context.vaoTracker.checkAlive(vaobj, "glDisableVertexArrayAttrib"); + VAO vao = context.vaos.get(vaobj); + if (vao != null && index > -1 && index < vao.enabledVertexArrays.length) { + vao.enabledVertexArrays[index] = false; + } } + org.lwjgl.opengl.ARBDirectStateAccess.glDisableVertexArrayAttrib(vaobj, index); } public static void glEnableVertexArrayAttrib(int vaobj, int index) { - org.lwjgl.opengl.ARBDirectStateAccess.glDisableVertexArrayAttrib(vaobj, index); if (Properties.VALIDATE.enabled) { Context context = Context.currentContext(); - context.vaos.get(vaobj).enabledVertexArrays[index] = true; + context.vaoTracker.checkAlive(vaobj, "glEnableVertexArrayAttrib"); + VAO vao = context.vaos.get(vaobj); + if (vao != null && index > -1 && index < vao.enabledVertexArrays.length) { + vao.enabledVertexArrays[index] = true; + } + } + org.lwjgl.opengl.ARBDirectStateAccess.glEnableVertexArrayAttrib(vaobj, index); + } + + public static int glCreateBuffers() { + int handle = org.lwjgl.opengl.ARBDirectStateAccess.glCreateBuffers(); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.bufferObjects.create(handle, new BufferObject()); + } + return handle; + } + + public static void glCreateBuffers(IntBuffer buffers) { + org.lwjgl.opengl.ARBDirectStateAccess.glCreateBuffers(buffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = buffers.position(); + for (int i = 0; i < buffers.remaining(); i++) { + ctx.shareGroup.bufferObjects.create(buffers.get(pos + i), new BufferObject()); + } + } + } + + public static void glCreateBuffers(int[] buffers) { + org.lwjgl.opengl.ARBDirectStateAccess.glCreateBuffers(buffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < buffers.length; i++) { + ctx.shareGroup.bufferObjects.create(buffers[i], new BufferObject()); + } + } + } + + public static int glCreateTextures(int target) { + int handle = org.lwjgl.opengl.ARBDirectStateAccess.glCreateTextures(target); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.textureObjects.create(handle, new TextureObject()); + } + return handle; + } + + public static void glCreateTextures(int target, IntBuffer textures) { + org.lwjgl.opengl.ARBDirectStateAccess.glCreateTextures(target, textures); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = textures.position(); + for (int i = 0; i < textures.remaining(); i++) { + ctx.shareGroup.textureObjects.create(textures.get(pos + i), new TextureObject()); + } + } + } + + public static void glCreateTextures(int target, int[] textures) { + org.lwjgl.opengl.ARBDirectStateAccess.glCreateTextures(target, textures); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < textures.length; i++) { + ctx.shareGroup.textureObjects.create(textures[i], new TextureObject()); + } + } + } + + public static int glCreateFramebuffers() { + int handle = org.lwjgl.opengl.ARBDirectStateAccess.glCreateFramebuffers(); + if (Properties.VALIDATE.enabled) { + FBO fbo = new FBO(handle); + Context ctx = Context.currentContext(); + ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); + } + return handle; + } + + public static void glCreateFramebuffers(int[] framebuffers) { + org.lwjgl.opengl.ARBDirectStateAccess.glCreateFramebuffers(framebuffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < framebuffers.length; i++) { + int handle = framebuffers[i]; + FBO fbo = new FBO(handle); + ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); + } + } + } + + public static void glCreateFramebuffers(IntBuffer framebuffers) { + org.lwjgl.opengl.ARBDirectStateAccess.glCreateFramebuffers(framebuffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = framebuffers.position(); + for (int i = 0; i < framebuffers.remaining(); i++) { + int handle = framebuffers.get(pos + i); + FBO fbo = new FBO(handle); + ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); + } + } + } + + public static int glCreateProgramPipelines() { + int handle = org.lwjgl.opengl.ARBDirectStateAccess.glCreateProgramPipelines(); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ProgramPipeline pp = new ProgramPipeline(); + ctx.programPipelines.put(handle, pp); + ctx.pipelineTracker.create(handle, pp); + } + return handle; + } + + public static void glCreateProgramPipelines(IntBuffer pipelines) { + org.lwjgl.opengl.ARBDirectStateAccess.glCreateProgramPipelines(pipelines); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = pipelines.position(); + for (int i = 0; i < pipelines.remaining(); i++) { + int handle = pipelines.get(pos + i); + ProgramPipeline pp = new ProgramPipeline(); + ctx.programPipelines.put(handle, pp); + ctx.pipelineTracker.create(handle, pp); + } + } + } + + public static void glCreateProgramPipelines(int[] pipelines) { + org.lwjgl.opengl.ARBDirectStateAccess.glCreateProgramPipelines(pipelines); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < pipelines.length; i++) { + int handle = pipelines[i]; + ProgramPipeline pp = new ProgramPipeline(); + ctx.programPipelines.put(handle, pp); + ctx.pipelineTracker.create(handle, pp); + } + } + } + + public static int glCreateRenderbuffers() { + int handle = org.lwjgl.opengl.ARBDirectStateAccess.glCreateRenderbuffers(); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + return handle; + } + + public static void glCreateRenderbuffers(IntBuffer renderbuffers) { + org.lwjgl.opengl.ARBDirectStateAccess.glCreateRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = renderbuffers.position(); + for (int i = 0; i < renderbuffers.remaining(); i++) { + int handle = renderbuffers.get(pos + i); + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + } + } + + public static void glCreateRenderbuffers(int[] renderbuffers) { + org.lwjgl.opengl.ARBDirectStateAccess.glCreateRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < renderbuffers.length; i++) { + int handle = renderbuffers[i]; + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + } + } + + public static void glNamedFramebufferTexture(int framebuffer, int attachment, int texture, int level) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.fboTracker.checkAlive(framebuffer, "glNamedFramebufferTexture"); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glNamedFramebufferTexture"); + } + Context.FBO fbo = ctx.fbos.get(framebuffer); + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.ARBDirectStateAccess.glNamedFramebufferTexture(framebuffer, attachment, texture, level); + } + + public static void glNamedFramebufferRenderbuffer(int framebuffer, int attachment, int renderbuffertarget, int renderbuffer) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.fboTracker.checkAlive(framebuffer, "glNamedFramebufferRenderbuffer"); + if (renderbuffer != 0) { + ctx.shareGroup.renderbufferObjects.checkAlive(renderbuffer, "glNamedFramebufferRenderbuffer"); + } + Context.FBO fbo = ctx.fbos.get(framebuffer); + if (fbo != null) { + fbo.attachRenderbuffer(attachment, renderbuffer); + } + } + org.lwjgl.opengl.ARBDirectStateAccess.glNamedFramebufferRenderbuffer(framebuffer, attachment, renderbuffertarget, renderbuffer); + } + + public static void glNamedFramebufferTextureLayer(int framebuffer, int attachment, int texture, int level, int layer) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.fboTracker.checkAlive(framebuffer, "glNamedFramebufferTextureLayer"); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glNamedFramebufferTextureLayer"); + } + Context.FBO fbo = ctx.fbos.get(framebuffer); + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.ARBDirectStateAccess.glNamedFramebufferTextureLayer(framebuffer, attachment, texture, level, layer); + } + + public static void glTextureBuffer(int texture, int internalformat, int buffer) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glTextureBuffer"); + } + if (buffer != 0) { + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glTextureBuffer"); + } + } + org.lwjgl.opengl.ARBDirectStateAccess.glTextureBuffer(texture, internalformat, buffer); + } + + public static void glTextureBufferRange(int texture, int internalformat, int buffer, long offset, long size) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glTextureBufferRange"); + } + if (buffer != 0) { + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glTextureBufferRange"); + } } + org.lwjgl.opengl.ARBDirectStateAccess.glTextureBufferRange(texture, internalformat, buffer, offset, size); } } diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/ARBFramebufferObject.java b/src/org/lwjglx/debug/org/lwjgl/opengl/ARBFramebufferObject.java index e24daf3..295f83f 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/ARBFramebufferObject.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/ARBFramebufferObject.java @@ -40,6 +40,7 @@ public static void glGenFramebuffers(IntBuffer framebuffers) { int handle = framebuffers.get(pos + i); FBO fbo = new FBO(handle); ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); } } } @@ -50,6 +51,7 @@ public static int glGenFramebuffers() { FBO fbo = new FBO(handle); Context ctx = Context.currentContext(); ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); } return handle; } @@ -62,6 +64,7 @@ public static void glGenFramebuffers(int[] framebuffers) { int handle = framebuffers[i]; FBO fbo = new FBO(handle); ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); } } } @@ -77,7 +80,19 @@ public static void glBindFramebuffer(int target, int framebuffer) { } } } - ctx.currentFbo = fbo; + if (framebuffer != 0) { + ctx.fboTracker.checkAlive(framebuffer, "glBindFramebuffer"); + } + if (target == org.lwjgl.opengl.ARBFramebufferObject.GL_DRAW_FRAMEBUFFER) { + ctx.currentDrawFbo = fbo; + ctx.currentFbo = fbo; + } else if (target == org.lwjgl.opengl.ARBFramebufferObject.GL_READ_FRAMEBUFFER) { + ctx.currentReadFbo = fbo; + } else { + ctx.currentDrawFbo = fbo; + ctx.currentReadFbo = fbo; + ctx.currentFbo = fbo; + } } org.lwjgl.opengl.ARBFramebufferObject.glBindFramebuffer(target, framebuffer); } @@ -85,50 +100,192 @@ public static void glBindFramebuffer(int target, int framebuffer) { public static void glDeleteFramebuffers(IntBuffer framebuffers) { org.lwjgl.opengl.ARBFramebufferObject.glDeleteFramebuffers(framebuffers); if (Properties.VALIDATE.enabled) { - Context context = Context.currentContext(); - int pos = framebuffers.position(); - for (int i = 0; i < framebuffers.remaining(); i++) { - int framebuffer = framebuffers.get(pos + i); - if (framebuffer == 0) - continue; - FBO fbo = context.fbos.get(framebuffer); - if (fbo != null && fbo == context.currentFbo) { - context.currentFbo = context.defaultFbo; - } - context.fbos.remove(framebuffer); - } + Context.deleteFramebuffers(framebuffers); } } public static void glDeleteFramebuffers(int framebuffer) { org.lwjgl.opengl.ARBFramebufferObject.glDeleteFramebuffers(framebuffer); if (Properties.VALIDATE.enabled) { - if (framebuffer == 0) - return; - Context context = Context.currentContext(); - FBO fbo = context.fbos.get(framebuffer); - if (fbo != null && fbo == context.currentFbo) { - context.currentFbo = context.defaultFbo; - } - context.fbos.remove(framebuffer); + Context.deleteFramebuffer(framebuffer); } } public static void glDeleteFramebuffers(int[] framebuffers) { org.lwjgl.opengl.ARBFramebufferObject.glDeleteFramebuffers(framebuffers); + if (Properties.VALIDATE.enabled) { + Context.deleteFramebuffers(framebuffers); + } + } + + public static void glGenRenderbuffers(IntBuffer renderbuffers) { + org.lwjgl.opengl.ARBFramebufferObject.glGenRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = renderbuffers.position(); + for (int i = 0; i < renderbuffers.remaining(); i++) { + int handle = renderbuffers.get(pos + i); + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + } + } + + public static int glGenRenderbuffers() { + int handle = org.lwjgl.opengl.ARBFramebufferObject.glGenRenderbuffers(); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + return handle; + } + + public static void glGenRenderbuffers(int[] renderbuffers) { + org.lwjgl.opengl.ARBFramebufferObject.glGenRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < renderbuffers.length; i++) { + int handle = renderbuffers[i]; + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + } + } + + public static void glBindRenderbuffer(int target, int renderbuffer) { + if (Properties.VALIDATE.enabled && renderbuffer != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.renderbufferObjects.checkAlive(renderbuffer, "glBindRenderbuffer"); + } + org.lwjgl.opengl.ARBFramebufferObject.glBindRenderbuffer(target, renderbuffer); + } + + public static void glDeleteRenderbuffers(IntBuffer renderbuffers) { + org.lwjgl.opengl.ARBFramebufferObject.glDeleteRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + Context.deleteRenderbuffers(renderbuffers); + } + } + + public static void glDeleteRenderbuffers(int renderbuffer) { + org.lwjgl.opengl.ARBFramebufferObject.glDeleteRenderbuffers(renderbuffer); + if (Properties.VALIDATE.enabled) { + Context.deleteRenderbuffer(renderbuffer); + } + } + + public static void glDeleteRenderbuffers(int[] renderbuffers) { + org.lwjgl.opengl.ARBFramebufferObject.glDeleteRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + Context.deleteRenderbuffers(renderbuffers); + } + } + + public static void glFramebufferTexture1D(int target, int attachment, int textarget, int texture, int level) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glFramebufferTexture1D"); + } + FBO fbo = (target == org.lwjgl.opengl.ARBFramebufferObject.GL_READ_FRAMEBUFFER) ? ctx.currentReadFbo : ctx.currentDrawFbo; + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.ARBFramebufferObject.glFramebufferTexture1D(target, attachment, textarget, texture, level); + } + + public static void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glFramebufferTexture2D"); + } + FBO fbo = (target == org.lwjgl.opengl.ARBFramebufferObject.GL_READ_FRAMEBUFFER) ? ctx.currentReadFbo : ctx.currentDrawFbo; + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.ARBFramebufferObject.glFramebufferTexture2D(target, attachment, textarget, texture, level); + } + + public static void glFramebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glFramebufferTexture3D"); + } + FBO fbo = (target == org.lwjgl.opengl.ARBFramebufferObject.GL_READ_FRAMEBUFFER) ? ctx.currentReadFbo : ctx.currentDrawFbo; + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.ARBFramebufferObject.glFramebufferTexture3D(target, attachment, textarget, texture, level, zoffset); + } + + public static void glFramebufferTextureLayer(int target, int attachment, int texture, int level, int layer) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glFramebufferTextureLayer"); + } + FBO fbo = (target == org.lwjgl.opengl.ARBFramebufferObject.GL_READ_FRAMEBUFFER) ? ctx.currentReadFbo : ctx.currentDrawFbo; + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.ARBFramebufferObject.glFramebufferTextureLayer(target, attachment, texture, level, layer); + } + + public static void glFramebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (renderbuffer != 0) { + ctx.shareGroup.renderbufferObjects.checkAlive(renderbuffer, "glFramebufferRenderbuffer"); + } + FBO fbo = (target == org.lwjgl.opengl.ARBFramebufferObject.GL_READ_FRAMEBUFFER) ? ctx.currentReadFbo : ctx.currentDrawFbo; + if (fbo != null) { + fbo.attachRenderbuffer(attachment, renderbuffer); + } + } + org.lwjgl.opengl.ARBFramebufferObject.glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer); + } + + public static void glBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { if (Properties.VALIDATE.enabled) { Context context = Context.currentContext(); - for (int i = 0; i < framebuffers.length; i++) { - int framebuffer = framebuffers[i]; - if (framebuffer == 0) - continue; - FBO fbo = context.fbos.get(framebuffer); - if (fbo != null && fbo == context.currentFbo) { - context.currentFbo = context.defaultFbo; + if (context.currentReadFbo != null) { + for (int tex : context.currentReadFbo.attachedTextures.values()) { + context.shareGroup.textureObjects.checkAlive(tex, "glBlitFramebuffer"); + } + for (int rb : context.currentReadFbo.attachedRenderbuffers.values()) { + context.shareGroup.renderbufferObjects.checkAlive(rb, "glBlitFramebuffer"); + } + } + if (context.currentDrawFbo != null) { + for (int tex : context.currentDrawFbo.attachedTextures.values()) { + context.shareGroup.textureObjects.checkAlive(tex, "glBlitFramebuffer"); + } + for (int rb : context.currentDrawFbo.attachedRenderbuffers.values()) { + context.shareGroup.renderbufferObjects.checkAlive(rb, "glBlitFramebuffer"); + } + } + } + org.lwjgl.opengl.ARBFramebufferObject.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + } + + public static int glCheckFramebufferStatus(int target) { + if (Properties.VALIDATE.enabled) { + Context context = Context.currentContext(); + FBO fbo = (target == org.lwjgl.opengl.ARBFramebufferObject.GL_READ_FRAMEBUFFER) ? context.currentReadFbo : context.currentDrawFbo; + if (fbo != null) { + for (int tex : fbo.attachedTextures.values()) { + context.shareGroup.textureObjects.checkAlive(tex, "glCheckFramebufferStatus"); + } + for (int rb : fbo.attachedRenderbuffers.values()) { + context.shareGroup.renderbufferObjects.checkAlive(rb, "glCheckFramebufferStatus"); } - context.fbos.remove(framebuffer); } } + return org.lwjgl.opengl.ARBFramebufferObject.glCheckFramebufferStatus(target); } } diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/ARBTextureBufferObject.java b/src/org/lwjglx/debug/org/lwjgl/opengl/ARBTextureBufferObject.java new file mode 100644 index 0000000..d2ee143 --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/ARBTextureBufferObject.java @@ -0,0 +1,15 @@ +package org.lwjglx.debug.org.lwjgl.opengl; + +import org.lwjglx.debug.Properties; + +public class ARBTextureBufferObject { + + public static void glTexBufferARB(int target, int internalformat, int buffer) { + if (Properties.VALIDATE.enabled && buffer != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glTexBufferARB"); + } + org.lwjgl.opengl.ARBTextureBufferObject.glTexBufferARB(target, internalformat, buffer); + } + +} diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/ARBTextureBufferRange.java b/src/org/lwjglx/debug/org/lwjgl/opengl/ARBTextureBufferRange.java new file mode 100644 index 0000000..b6d317d --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/ARBTextureBufferRange.java @@ -0,0 +1,28 @@ +package org.lwjglx.debug.org.lwjgl.opengl; + +import org.lwjglx.debug.Properties; + +public class ARBTextureBufferRange { + + public static void glTexBufferRange(int target, int internalformat, int buffer, long offset, long size) { + if (Properties.VALIDATE.enabled && buffer != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glTexBufferRange"); + } + org.lwjgl.opengl.ARBTextureBufferRange.glTexBufferRange(target, internalformat, buffer, offset, size); + } + + public static void glTextureBufferRangeEXT(int texture, int target, int internalformat, int buffer, long offset, long size) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glTextureBufferRangeEXT"); + } + if (buffer != 0) { + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glTextureBufferRangeEXT"); + } + } + org.lwjgl.opengl.ARBTextureBufferRange.glTextureBufferRangeEXT(texture, target, internalformat, buffer, offset, size); + } + +} diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/ARBVertexArrayObject.java b/src/org/lwjglx/debug/org/lwjgl/opengl/ARBVertexArrayObject.java index 51c8563..db1fb92 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/ARBVertexArrayObject.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/ARBVertexArrayObject.java @@ -38,8 +38,10 @@ public static void glGenVertexArrays(IntBuffer arrays) { Context context = Context.currentContext(); int position = arrays.position(); for (int i = 0; i < arrays.remaining(); i++) { + int id = arrays.get(position + i); VAO vao = new VAO(context.GL_MAX_VERTEX_ATTRIBS); - context.vaos.put(arrays.get(position + i), vao); + context.vaos.put(id, vao); + context.vaoTracker.create(id, vao); } } } @@ -50,6 +52,7 @@ public static int glGenVertexArrays() { Context context = Context.currentContext(); VAO vao = new VAO(context.GL_MAX_VERTEX_ATTRIBS); context.vaos.put(index, vao); + context.vaoTracker.create(index, vao); } return index; } @@ -59,8 +62,10 @@ public static void glGenVertexArrays(int[] arrays) { if (Properties.VALIDATE.enabled) { Context context = Context.currentContext(); for (int i = 0; i < arrays.length; i++) { + int id = arrays[i]; VAO vao = new VAO(context.GL_MAX_VERTEX_ATTRIBS); - context.vaos.put(arrays[i], vao); + context.vaos.put(id, vao); + context.vaoTracker.create(id, vao); } } } @@ -76,6 +81,9 @@ public static void glBindVertexArray(int index) { } } } + if (index != 0) { + ctx.vaoTracker.checkAlive(index, "glBindVertexArray"); + } ctx.currentVao = vao; } org.lwjgl.opengl.ARBVertexArrayObject.glBindVertexArray(index); diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/Context.java b/src/org/lwjglx/debug/org/lwjgl/opengl/Context.java index 2131d62..1dcf213 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/Context.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/Context.java @@ -24,12 +24,14 @@ of this software and associated documentation files (the "Software"), to deal import org.lwjglx.debug.Properties; import org.lwjglx.debug.RT; +import org.lwjglx.debug.ResourceState; +import org.lwjglx.debug.ResourceTracker; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.IntBuffer; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -42,9 +44,26 @@ of this software and associated documentation files (the "Software"), to deal public class Context implements Comparable { public static class ShareGroup { - public Map bufferObjects = new HashMap<>(); - public Map textureObjects = new HashMap<>(); - public Set contexts = new ConcurrentSkipListSet(); + public final ResourceTracker bufferObjects = new ResourceTracker<>("Buffer"); + public final ResourceTracker textureObjects = new ResourceTracker<>("Texture"); + public final ResourceTracker renderbufferObjects = new ResourceTracker<>("Renderbuffer"); + public final ResourceTracker shaderObjects = new ResourceTracker<>("Shader"); + public final ResourceTracker programObjects = new ResourceTracker<>("Program"); + public final Set contexts = new ConcurrentSkipListSet(); + } + + public static class RenderbufferObject { + } + + public static class ShaderObject { + public final int type; + public ShaderObject(int type) { + this.type = type; + } + } + + public static class ProgramObject { + public final Set attachedShaders = ConcurrentHashMap.newKeySet(); } public static class VAO { @@ -67,9 +86,31 @@ public VAO(int GL_MAX_VERTEX_ATTRIBS) { public static class FBO { public int handle; + public final Map attachedTextures = new ConcurrentHashMap(); + public final Map attachedRenderbuffers = new ConcurrentHashMap(); public FBO(int handle) { this.handle = handle; } + + public void attachTexture(int attachment, int texture) { + if (texture != 0) { + attachedTextures.put(attachment, texture); + attachedRenderbuffers.remove(attachment); + } else { + attachedTextures.remove(attachment); + attachedRenderbuffers.remove(attachment); + } + } + + public void attachRenderbuffer(int attachment, int renderbuffer) { + if (renderbuffer != 0) { + attachedRenderbuffers.put(attachment, renderbuffer); + attachedTextures.remove(attachment); + } else { + attachedTextures.remove(attachment); + attachedRenderbuffers.remove(attachment); + } + } } public static class ProgramPipeline { @@ -137,13 +178,19 @@ public static class TimedCodeSection { public VAO currentVao; public FBO defaultFbo; public FBO currentFbo; + public FBO currentDrawFbo; + public FBO currentReadFbo; public ProgramPipeline defaultProgramPipeline; public ProgramPipeline currentProgramPipeline; - public Map vaos = new HashMap(); - public Map fbos = new HashMap(); - public Map bufferObjectBindings = new HashMap<>(); - public Map textureObjectBindings = new HashMap<>(); - public Map programPipelines = new HashMap<>(); + public final ResourceTracker vaoTracker = new ResourceTracker<>("VAO"); + public final ResourceTracker fboTracker = new ResourceTracker<>("FBO"); + public final ResourceTracker pipelineTracker = new ResourceTracker<>("ProgramPipeline"); + public int currentProgram; + public Map vaos = new ConcurrentHashMap(); + public Map fbos = new ConcurrentHashMap(); + public Map bufferObjectBindings = new ConcurrentHashMap<>(); + public Map textureObjectBindings = new ConcurrentHashMap<>(); + public Map programPipelines = new ConcurrentHashMap<>(); public ShareGroup shareGroup; public boolean inImmediateMode; public Thread currentInThread; @@ -235,12 +282,17 @@ public void init(int GL_MAX_VERTEX_ATTRIBS) { this.defaultVao = new VAO(GL_MAX_VERTEX_ATTRIBS); this.currentVao = defaultVao; this.vaos.put(0, defaultVao); + this.vaoTracker.create(0, defaultVao); this.defaultFbo = new FBO(0); this.currentFbo = defaultFbo; + this.currentDrawFbo = defaultFbo; + this.currentReadFbo = defaultFbo; this.fbos.put(0, defaultFbo); + this.fboTracker.create(0, defaultFbo); this.defaultProgramPipeline = new ProgramPipeline(); this.currentProgramPipeline = defaultProgramPipeline; this.programPipelines.put(0, defaultProgramPipeline); + this.pipelineTracker.create(0, defaultProgramPipeline); StringBuilder sb = new StringBuilder(); sb.append("Initialized OpenGL context for window[").append(this.counter).append("]\n"); sb.append(" Effective OpenGL version: ").append(openglVersion()).append("\n"); @@ -272,17 +324,91 @@ public void init(int GL_MAX_VERTEX_ATTRIBS) { public void destroy() { info("Destroying OpenGL context for window[" + this.counter + "]"); - if (shareGroup != null) { - shareGroup.contexts.remove(this); - SHARE_GROUPS.remove(window); - shareGroup = null; + try { + auditLeaks(); + } finally { + if (shareGroup != null) { + shareGroup.contexts.remove(this); + SHARE_GROUPS.remove(window); + shareGroup = null; + } + if (debugCallback != null) { + /* Can happen when we never actually called GL.createCapabilities() */ + debugCallback.free(); + } + } + } + + private void auditLeaks() { + if (!Properties.VALIDATE.enabled) { + return; + } + List leakMessages = new ArrayList<>(); + int liveVaos = vaoTracker.liveCountExcludingDefault(); + if (liveVaos > 0) { + leakMessages.add("OpenGL context for window[" + this.counter + "] destroyed with " + liveVaos + " un-deleted VAO(s)" + + leakDetails(vaoTracker.liveEntriesExcludingDefault())); } - if (debugCallback != null) { - /* Can happen when we never actually called GL.createCapabilities() */ - debugCallback.free(); + int liveFbos = fboTracker.liveCountExcludingDefault(); + if (liveFbos > 0) { + leakMessages.add("OpenGL context for window[" + this.counter + "] destroyed with " + liveFbos + " un-deleted FBO(s)" + + leakDetails(fboTracker.liveEntriesExcludingDefault())); + } + int livePipelines = pipelineTracker.liveCountExcludingDefault(); + if (livePipelines > 0) { + leakMessages.add("OpenGL context for window[" + this.counter + "] destroyed with " + livePipelines + " un-deleted ProgramPipeline(s)" + + leakDetails(pipelineTracker.liveEntriesExcludingDefault())); + } + if (shareGroup != null && shareGroup.contexts.size() <= 1) { + int liveBuffers = shareGroup.bufferObjects.liveCountExcludingDefault(); + if (liveBuffers > 0) { + leakMessages.add("ShareGroup destroyed with " + liveBuffers + " un-deleted Buffer(s)" + + leakDetails(shareGroup.bufferObjects.liveEntriesExcludingDefault())); + } + int liveTextures = shareGroup.textureObjects.liveCountExcludingDefault(); + if (liveTextures > 0) { + leakMessages.add("ShareGroup destroyed with " + liveTextures + " un-deleted Texture(s)" + + leakDetails(shareGroup.textureObjects.liveEntriesExcludingDefault())); + } + int liveRenderbuffers = shareGroup.renderbufferObjects.liveCountExcludingDefault(); + if (liveRenderbuffers > 0) { + leakMessages.add("ShareGroup destroyed with " + liveRenderbuffers + " un-deleted Renderbuffer(s)" + + leakDetails(shareGroup.renderbufferObjects.liveEntriesExcludingDefault())); + } + int liveShaders = shareGroup.shaderObjects.liveCountExcludingDefault(); + if (liveShaders > 0) { + leakMessages.add("ShareGroup destroyed with " + liveShaders + " un-deleted Shader(s)" + + leakDetails(shareGroup.shaderObjects.liveEntriesExcludingDefault())); + } + int livePrograms = shareGroup.programObjects.liveCountExcludingDefault(); + if (livePrograms > 0) { + leakMessages.add("ShareGroup destroyed with " + livePrograms + " un-deleted Program(s)" + + leakDetails(shareGroup.programObjects.liveEntriesExcludingDefault())); + } + } + for (String msg : leakMessages) { + if (Properties.FAIL_ON_LEAKS.enabled) { + RT.throwISEOrLogError(msg); + } else { + org.lwjglx.debug.Log.warn(msg); + } } } + private static String leakDetails(List> entries) { + if (!Properties.STRICT.enabled || entries == null || entries.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (ResourceTracker.Entry e : entries) { + sb.append("\n Handle [").append(e.handle).append("]"); + if (e.creationSite != null) { + sb.append(" created at: ").append(ResourceTracker.formatTrace(e.creationSite)); + } + } + return sb.toString(); + } + @Override public int compareTo(Context o) { if (this.window < o.window) @@ -296,25 +422,23 @@ public static void deleteVertexArray(int index) { if (index == 0) return; Context context = currentContext(); + context.vaoTracker.delete(index, "glDeleteVertexArrays"); VAO vao = context.vaos.get(index); if (vao != null && vao == context.currentVao) { context.currentVao = context.defaultVao; } - context.vaos.remove(index); } public static void deleteVertexArrays(IntBuffer indices) { - Context context = currentContext(); int pos = indices.position(); for (int i = 0; i < indices.remaining(); i++) { - int index = indices.get(pos + i); - if (index == 0) - continue; - VAO vao = context.vaos.get(index); - if (vao != null && vao == context.currentVao) { - context.currentVao = context.defaultVao; - } - context.vaos.remove(index); + deleteVertexArray(indices.get(pos + i)); + } + } + + public static void deleteVertexArrays(int[] indices) { + for (int i = 0; i < indices.length; i++) { + deleteVertexArray(indices[i]); } } @@ -322,53 +446,83 @@ public static void deletePipeline(int index) { if (index == 0) return; Context context = currentContext(); + context.pipelineTracker.delete(index, "glDeleteProgramPipelines"); ProgramPipeline pp = context.programPipelines.get(index); if (pp != null && pp == context.currentProgramPipeline) { context.currentProgramPipeline = context.defaultProgramPipeline; } - context.programPipelines.remove(index); } public static void deletePipelines(IntBuffer pipelines) { - Context context = currentContext(); int pos = pipelines.position(); for (int i = 0; i < pipelines.remaining(); i++) { - int index = pipelines.get(pos + i); - if (index == 0) - continue; - ProgramPipeline pp = context.programPipelines.get(index); - if (pp != null && pp == context.currentProgramPipeline) { - context.currentProgramPipeline = context.defaultProgramPipeline; - } - context.programPipelines.remove(index); + deletePipeline(pipelines.get(pos + i)); } } public static void deletePipelines(int[] pipelines) { - Context context = currentContext(); for (int i = 0; i < pipelines.length; i++) { - int index = pipelines[i]; - if (index == 0) - continue; - ProgramPipeline pp = context.programPipelines.get(index); - if (pp != null && pp == context.currentProgramPipeline) { - context.currentProgramPipeline = context.defaultProgramPipeline; + deletePipeline(pipelines[i]); + } + } + + public static void deleteFramebuffer(int index) { + if (index == 0) + return; + Context context = currentContext(); + context.fboTracker.delete(index, "glDeleteFramebuffers"); + FBO fbo = context.fbos.remove(index); + if (fbo != null) { + fbo.attachedTextures.clear(); + fbo.attachedRenderbuffers.clear(); + if (fbo == context.currentDrawFbo) { + context.currentDrawFbo = context.defaultFbo; } - context.programPipelines.remove(index); + if (fbo == context.currentReadFbo) { + context.currentReadFbo = context.defaultFbo; + } + context.currentFbo = context.currentDrawFbo; } } - public static void deleteVertexArrays(int[] indices) { + public static void deleteFramebuffers(IntBuffer framebuffers) { + int pos = framebuffers.position(); + for (int i = 0; i < framebuffers.remaining(); i++) { + deleteFramebuffer(framebuffers.get(pos + i)); + } + } + + public static void deleteFramebuffers(int[] framebuffers) { + for (int i = 0; i < framebuffers.length; i++) { + deleteFramebuffer(framebuffers[i]); + } + } + + public static void deleteRenderbuffer(int index) { + if (index == 0) + return; Context context = currentContext(); - for (int i = 0; i < indices.length; i++) { - int index = indices[i]; - if (index == 0) - continue; - VAO vao = context.vaos.get(index); - if (vao != null && vao == context.currentVao) { - context.currentVao = context.defaultVao; + context.shareGroup.renderbufferObjects.delete(index, "glDeleteRenderbuffers"); + Set contexts = context.shareGroup != null ? context.shareGroup.contexts : Collections.singleton(context); + for (Context c : contexts) { + for (FBO fbo : c.fbos.values()) { + if (fbo.attachedRenderbuffers.values().removeIf(r -> r == index)) { + org.lwjglx.debug.Log.warn("Renderbuffer [" + index + "] deleted while still attached to FBO [" + fbo.handle + "]"); + } } - context.vaos.remove(index); + } + } + + public static void deleteRenderbuffers(IntBuffer indices) { + int pos = indices.position(); + for (int i = 0; i < indices.remaining(); i++) { + deleteRenderbuffer(indices.get(pos + i)); + } + } + + public static void deleteRenderbuffers(int[] indices) { + for (int i = 0; i < indices.length; i++) { + deleteRenderbuffer(indices[i]); } } @@ -402,11 +556,18 @@ public static void checkVertexAttributes() { public static void checkFramebufferCompleteness() { if (Properties.VALIDATE.enabled) { Context context = currentContext(); - if (context.currentFbo != null) { + FBO fbo = context.currentDrawFbo; + if (fbo != null) { + for (int tex : fbo.attachedTextures.values()) { + context.shareGroup.textureObjects.checkAlive(tex, "glCheckFramebufferStatus"); + } + for (int rb : fbo.attachedRenderbuffers.values()) { + context.shareGroup.renderbufferObjects.checkAlive(rb, "glCheckFramebufferStatus"); + } /* Check framebuffer status */ int status = org.lwjgl.opengl.GL30.glCheckFramebufferStatus(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER); if (status != org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_COMPLETE) { - RT.throwISEOrLogError("Framebuffer [" + context.currentFbo.handle + "] is not complete: " + status); + RT.throwISEOrLogError("Framebuffer [" + fbo.handle + "] is not complete: " + status); } } } diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/EXTFramebufferObject.java b/src/org/lwjglx/debug/org/lwjgl/opengl/EXTFramebufferObject.java index 87da08e..83ba97a 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/EXTFramebufferObject.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/EXTFramebufferObject.java @@ -77,6 +77,8 @@ public static void glBindFramebufferEXT(int target, int framebuffer) { } } } + ctx.currentDrawFbo = fbo; + ctx.currentReadFbo = fbo; ctx.currentFbo = fbo; } org.lwjgl.opengl.EXTFramebufferObject.glBindFramebufferEXT(target, framebuffer); @@ -92,8 +94,14 @@ public static void glDeleteFramebuffersEXT(IntBuffer framebuffers) { if (framebuffer == 0) continue; FBO fbo = context.fbos.get(framebuffer); - if (fbo != null && fbo == context.currentFbo) { - context.currentFbo = context.defaultFbo; + if (fbo != null) { + if (fbo == context.currentDrawFbo) { + context.currentDrawFbo = context.defaultFbo; + } + if (fbo == context.currentReadFbo) { + context.currentReadFbo = context.defaultFbo; + } + context.currentFbo = context.currentDrawFbo; } context.fbos.remove(framebuffer); } @@ -107,8 +115,14 @@ public static void glDeleteFramebuffersEXT(int framebuffer) { return; Context context = Context.currentContext(); FBO fbo = context.fbos.get(framebuffer); - if (fbo != null && fbo == context.currentFbo) { - context.currentFbo = context.defaultFbo; + if (fbo != null) { + if (fbo == context.currentDrawFbo) { + context.currentDrawFbo = context.defaultFbo; + } + if (fbo == context.currentReadFbo) { + context.currentReadFbo = context.defaultFbo; + } + context.currentFbo = context.currentDrawFbo; } context.fbos.remove(framebuffer); } @@ -123,8 +137,14 @@ public static void glDeleteFramebuffersEXT(int[] framebuffers) { if (framebuffer == 0) continue; FBO fbo = context.fbos.get(framebuffer); - if (fbo != null && fbo == context.currentFbo) { - context.currentFbo = context.defaultFbo; + if (fbo != null) { + if (fbo == context.currentDrawFbo) { + context.currentDrawFbo = context.defaultFbo; + } + if (fbo == context.currentReadFbo) { + context.currentReadFbo = context.defaultFbo; + } + context.currentFbo = context.currentDrawFbo; } context.fbos.remove(framebuffer); } diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/GL11.java b/src/org/lwjglx/debug/org/lwjgl/opengl/GL11.java index 3636b03..8a1fa00 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/GL11.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/GL11.java @@ -31,6 +31,8 @@ of this software and associated documentation files (the "Software"), to deal import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; +import java.util.Collections; +import java.util.Set; import org.lwjglx.debug.*; @@ -309,30 +311,97 @@ public static void glPopClientAttrib() { public static void glGenTextures(IntBuffer textures) { org.lwjgl.opengl.GL11.glGenTextures(textures); - Context ctx = Context.currentContext(); - int pos = textures.position(); - for (int i = 0; i < textures.remaining(); i++) { - int texture = textures.get(pos + i); - ctx.shareGroup.textureObjects.put(texture, new TextureObject()); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = textures.position(); + for (int i = 0; i < textures.remaining(); i++) { + int texture = textures.get(pos + i); + if (texture != 0) { + TextureObject to = new TextureObject(); + ctx.shareGroup.textureObjects.create(texture, to); + ctx.textureObjectBindings.put(texture, to); + } + } } } public static void glGenTextures(int[] textures) { org.lwjgl.opengl.GL11.glGenTextures(textures); - Context ctx = Context.currentContext(); - for (int i = 0; i < textures.length; i++) { - int texture = textures[i]; - ctx.shareGroup.textureObjects.put(texture, new TextureObject()); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < textures.length; i++) { + int texture = textures[i]; + if (texture != 0) { + TextureObject to = new TextureObject(); + ctx.shareGroup.textureObjects.create(texture, to); + ctx.textureObjectBindings.put(texture, to); + } + } } } public static int glGenTextures() { int tex = org.lwjgl.opengl.GL11.glGenTextures(); - Context ctx = Context.currentContext(); - ctx.shareGroup.textureObjects.put(tex, new TextureObject()); + if (Properties.VALIDATE.enabled && tex != 0) { + Context ctx = Context.currentContext(); + TextureObject to = new TextureObject(); + ctx.shareGroup.textureObjects.create(tex, to); + ctx.textureObjectBindings.put(tex, to); + } return tex; } + private static void deleteTexture(Context ctx, int texture) { + if (texture != 0) { + ctx.shareGroup.textureObjects.delete(texture, "glDeleteTextures"); + ctx.textureObjectBindings.remove(texture); + Set contexts = ctx.shareGroup != null ? ctx.shareGroup.contexts : Collections.singleton(ctx); + for (Context c : contexts) { + for (Context.FBO fbo : c.fbos.values()) { + if (fbo.attachedTextures.values().removeIf(t -> t == texture)) { + Log.warn("Texture [" + texture + "] deleted while still attached to FBO [" + fbo.handle + "]"); + } + } + } + } + } + + public static void glDeleteTextures(int texture) { + if (Properties.VALIDATE.enabled) { + deleteTexture(Context.currentContext(), texture); + } + org.lwjgl.opengl.GL11.glDeleteTextures(texture); + } + + public static void glDeleteTextures(IntBuffer textures) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = textures.position(); + for (int i = 0; i < textures.remaining(); i++) { + deleteTexture(ctx, textures.get(pos + i)); + } + } + org.lwjgl.opengl.GL11.glDeleteTextures(textures); + } + + public static void glDeleteTextures(int[] textures) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < textures.length; i++) { + deleteTexture(ctx, textures[i]); + } + } + org.lwjgl.opengl.GL11.glDeleteTextures(textures); + } + + public static void glBindTexture(int target, int texture) { + if (Properties.VALIDATE.enabled && texture != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.textureObjects.checkAlive(texture, "glBindTexture"); + } + org.lwjgl.opengl.GL11.glBindTexture(target, texture); + } + private static void glTexImage2D_trace(int target, int level, int internalformat, int width, int height, int border, int format, int type, Object pixelsOrSize, MethodCall mc) { mc.paramEnum(GLmetadata.TextureTarget().get(target)); mc.param(level); diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/GL15.java b/src/org/lwjglx/debug/org/lwjgl/opengl/GL15.java new file mode 100644 index 0000000..9e07b05 --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/GL15.java @@ -0,0 +1,152 @@ +package org.lwjglx.debug.org.lwjgl.opengl; + +import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.nio.ShortBuffer; + +import org.lwjglx.debug.Properties; +import org.lwjglx.debug.RT; +import org.lwjglx.debug.org.lwjgl.opengl.Context.BufferObject; + +public class GL15 { + + public static void glGenBuffers(IntBuffer buffers) { + org.lwjgl.opengl.GL15.glGenBuffers(buffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = buffers.position(); + for (int i = 0; i < buffers.remaining(); i++) { + int b = buffers.get(pos + i); + if (b != 0) { + BufferObject bo = new BufferObject(); + ctx.shareGroup.bufferObjects.create(b, bo); + } + } + } + } + + public static void glGenBuffers(int[] buffers) { + org.lwjgl.opengl.GL15.glGenBuffers(buffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < buffers.length; i++) { + int b = buffers[i]; + if (b != 0) { + BufferObject bo = new BufferObject(); + ctx.shareGroup.bufferObjects.create(b, bo); + } + } + } + } + + public static int glGenBuffers() { + int b = org.lwjgl.opengl.GL15.glGenBuffers(); + if (Properties.VALIDATE.enabled && b != 0) { + Context ctx = Context.currentContext(); + BufferObject bo = new BufferObject(); + ctx.shareGroup.bufferObjects.create(b, bo); + } + return b; + } + + public static void glDeleteBuffers(int buffer) { + if (Properties.VALIDATE.enabled && buffer != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.bufferObjects.delete(buffer, "glDeleteBuffers"); + ctx.bufferObjectBindings.remove(buffer); + } + org.lwjgl.opengl.GL15.glDeleteBuffers(buffer); + } + + public static void glDeleteBuffers(IntBuffer buffers) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = buffers.position(); + for (int i = 0; i < buffers.remaining(); i++) { + int b = buffers.get(pos + i); + if (b != 0) { + ctx.shareGroup.bufferObjects.delete(b, "glDeleteBuffers"); + ctx.bufferObjectBindings.remove(b); + } + } + } + org.lwjgl.opengl.GL15.glDeleteBuffers(buffers); + } + + public static void glDeleteBuffers(int[] buffers) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < buffers.length; i++) { + int b = buffers[i]; + if (b != 0) { + ctx.shareGroup.bufferObjects.delete(b, "glDeleteBuffers"); + ctx.bufferObjectBindings.remove(b); + } + } + } + org.lwjgl.opengl.GL15.glDeleteBuffers(buffers); + } + + public static void glBindBuffer(int target, int buffer) { + if (Properties.VALIDATE.enabled && buffer != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glBindBuffer"); + } + org.lwjgl.opengl.GL15.glBindBuffer(target, buffer); + } + + public static boolean glIsBuffer(int buffer) { + return org.lwjgl.opengl.GL15.glIsBuffer(buffer); + } + + public static void glBufferData(int target, long size, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, size, usage); + } + + public static void glBufferData(int target, ByteBuffer data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } + + public static void glBufferData(int target, ShortBuffer data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } + + public static void glBufferData(int target, IntBuffer data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } + + public static void glBufferData(int target, LongBuffer data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } + + public static void glBufferData(int target, FloatBuffer data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } + + public static void glBufferData(int target, DoubleBuffer data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } + + public static void glBufferData(int target, short[] data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } + + public static void glBufferData(int target, int[] data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } + + public static void glBufferData(int target, long[] data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } + + public static void glBufferData(int target, float[] data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } + + public static void glBufferData(int target, double[] data, int usage) { + org.lwjgl.opengl.GL15.glBufferData(target, data, usage); + } +} diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/GL20.java b/src/org/lwjglx/debug/org/lwjgl/opengl/GL20.java index f24f7e3..12d0354 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/GL20.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/GL20.java @@ -103,7 +103,88 @@ public static void glVertexAttribPointer(int index, int size, int type, boolean org.lwjgl.opengl.GL20.glVertexAttribPointer(index, size, type, normalized, stride, pointer); } + public static int glCreateShader(int type) { + int shader = org.lwjgl.opengl.GL20.glCreateShader(type); + if (Properties.VALIDATE.enabled && shader != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.shaderObjects.create(shader, new Context.ShaderObject(type)); + } + return shader; + } + + public static void glDeleteShader(int shader) { + if (Properties.VALIDATE.enabled && shader != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.shaderObjects.delete(shader, "glDeleteShader"); + for (ResourceTracker.Entry pe : ctx.shareGroup.programObjects.entries().values()) { + if (pe.object != null && pe.object.attachedShaders.remove(shader)) { + Log.warn("Shader [" + shader + "] deleted while still attached to Program [" + pe.handle + "]"); + } + } + } + org.lwjgl.opengl.GL20.glDeleteShader(shader); + } + + public static int glCreateProgram() { + int program = org.lwjgl.opengl.GL20.glCreateProgram(); + if (Properties.VALIDATE.enabled && program != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.programObjects.create(program, new Context.ProgramObject()); + } + return program; + } + + public static void glDeleteProgram(int program) { + if (Properties.VALIDATE.enabled && program != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.programObjects.delete(program, "glDeleteProgram"); + if (ctx.currentProgram == program) { + ctx.currentProgram = 0; + } + } + org.lwjgl.opengl.GL20.glDeleteProgram(program); + } + + public static void glUseProgram(int program) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (program != 0) { + ctx.shareGroup.programObjects.checkAlive(program, "glUseProgram"); + } + ctx.currentProgram = program; + } + org.lwjgl.opengl.GL20.glUseProgram(program); + } + + public static void glAttachShader(int program, int shader) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ResourceTracker.Entry pe = ctx.shareGroup.programObjects.checkAlive(program, "glAttachShader"); + ctx.shareGroup.shaderObjects.checkAlive(shader, "glAttachShader"); + if (pe != null && pe.object != null) { + pe.object.attachedShaders.add(shader); + } + } + org.lwjgl.opengl.GL20.glAttachShader(program, shader); + } + + public static void glDetachShader(int program, int shader) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ResourceTracker.Entry pe = ctx.shareGroup.programObjects.checkAlive(program, "glDetachShader"); + ctx.shareGroup.shaderObjects.checkAlive(shader, "glDetachShader"); + if (pe != null && pe.object != null) { + pe.object.attachedShaders.remove(shader); + } + } + org.lwjgl.opengl.GL20.glDetachShader(program, shader); + } + public static void glCompileShader(int shader) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.shaderObjects.checkAlive(shader, "glCompileShader"); + } org.lwjgl.opengl.GL20.glCompileShader(shader); if (Properties.VALIDATE.enabled) { /* Check compile status */ @@ -116,6 +197,10 @@ public static void glCompileShader(int shader) { } public static void glLinkProgram(int program) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.programObjects.checkAlive(program, "glLinkProgram"); + } org.lwjgl.opengl.GL20.glLinkProgram(program); if (Properties.VALIDATE.enabled) { /* Check link status */ @@ -127,7 +212,50 @@ public static void glLinkProgram(int program) { } } + public static void glValidateProgram(int program) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.programObjects.checkAlive(program, "glValidateProgram"); + } + org.lwjgl.opengl.GL20.glValidateProgram(program); + } + + public static int glGetUniformLocation(int program, CharSequence name) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.programObjects.checkAlive(program, "glGetUniformLocation"); + } + return org.lwjgl.opengl.GL20.glGetUniformLocation(program, name); + } + + public static int glGetUniformLocation(int program, ByteBuffer name) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.programObjects.checkAlive(program, "glGetUniformLocation"); + } + return org.lwjgl.opengl.GL20.glGetUniformLocation(program, name); + } + + public static int glGetAttribLocation(int program, CharSequence name) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.programObjects.checkAlive(program, "glGetAttribLocation"); + } + return org.lwjgl.opengl.GL20.glGetAttribLocation(program, name); + } + + public static int glGetAttribLocation(int program, ByteBuffer name) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.programObjects.checkAlive(program, "glGetAttribLocation"); + } + return org.lwjgl.opengl.GL20.glGetAttribLocation(program, name); + } + public static void glShaderSource(int shader, org.lwjgl.PointerBuffer strings, IntBuffer length) { + if (Properties.VALIDATE.enabled) { + Context.currentContext().shareGroup.shaderObjects.checkAlive(shader, "glShaderSource"); + } org.lwjgl.opengl.GL20.glShaderSource(shader, strings, length); if (TRACE.enabled) { /* Log the shader source */ @@ -147,6 +275,9 @@ public static void glShaderSource(int shader, org.lwjgl.PointerBuffer strings, I } public static void glShaderSource(int shader, CharSequence... strings) { + if (Properties.VALIDATE.enabled) { + Context.currentContext().shareGroup.shaderObjects.checkAlive(shader, "glShaderSource"); + } org.lwjgl.opengl.GL20.glShaderSource(shader, strings); if (TRACE.enabled) { /* Log the shader source */ @@ -161,6 +292,9 @@ public static void glShaderSource(int shader, CharSequence... strings) { } public static void glShaderSource(int shader, CharSequence string) { + if (Properties.VALIDATE.enabled) { + Context.currentContext().shareGroup.shaderObjects.checkAlive(shader, "glShaderSource"); + } org.lwjgl.opengl.GL20.glShaderSource(shader, string); if (TRACE.enabled) { /* Log the shader source */ diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/GL30.java b/src/org/lwjglx/debug/org/lwjgl/opengl/GL30.java index 072f734..f8e0885 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/GL30.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/GL30.java @@ -71,8 +71,10 @@ public static void glGenVertexArrays(IntBuffer arrays) { Context context = Context.currentContext(); int position = arrays.position(); for (int i = 0; i < arrays.remaining(); i++) { + int id = arrays.get(position + i); VAO vao = new VAO(context.GL_MAX_VERTEX_ATTRIBS); - context.vaos.put(arrays.get(position + i), vao); + context.vaos.put(id, vao); + context.vaoTracker.create(id, vao); } } } @@ -83,6 +85,7 @@ public static int glGenVertexArrays() { Context context = Context.currentContext(); VAO vao = new VAO(context.GL_MAX_VERTEX_ATTRIBS); context.vaos.put(index, vao); + context.vaoTracker.create(index, vao); } return index; } @@ -92,8 +95,10 @@ public static void glGenVertexArrays(int[] arrays) { if (Properties.VALIDATE.enabled) { Context context = Context.currentContext(); for (int i = 0; i < arrays.length; i++) { + int id = arrays[i]; VAO vao = new VAO(context.GL_MAX_VERTEX_ATTRIBS); - context.vaos.put(arrays[i], vao); + context.vaos.put(id, vao); + context.vaoTracker.create(id, vao); } } } @@ -109,6 +114,9 @@ public static void glBindVertexArray(int index) { } } } + if (index != 0) { + ctx.vaoTracker.checkAlive(index, "glBindVertexArray"); + } ctx.currentVao = vao; } org.lwjgl.opengl.GL30.glBindVertexArray(index); @@ -144,6 +152,7 @@ public static void glGenFramebuffers(IntBuffer framebuffers) { int handle = framebuffers.get(pos + i); FBO fbo = new FBO(handle); ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); } } } @@ -154,6 +163,7 @@ public static int glGenFramebuffers() { FBO fbo = new FBO(handle); Context ctx = Context.currentContext(); ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); } return handle; } @@ -166,6 +176,7 @@ public static void glGenFramebuffers(int[] framebuffers) { int handle = framebuffers[i]; FBO fbo = new FBO(handle); ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); } } } @@ -181,7 +192,19 @@ public static void glBindFramebuffer(int target, int framebuffer) { } } } - ctx.currentFbo = fbo; + if (framebuffer != 0) { + ctx.fboTracker.checkAlive(framebuffer, "glBindFramebuffer"); + } + if (target == org.lwjgl.opengl.GL30.GL_DRAW_FRAMEBUFFER) { + ctx.currentDrawFbo = fbo; + ctx.currentFbo = fbo; + } else if (target == org.lwjgl.opengl.GL30.GL_READ_FRAMEBUFFER) { + ctx.currentReadFbo = fbo; + } else { + ctx.currentDrawFbo = fbo; + ctx.currentReadFbo = fbo; + ctx.currentFbo = fbo; + } } org.lwjgl.opengl.GL30.glBindFramebuffer(target, framebuffer); } @@ -189,50 +212,208 @@ public static void glBindFramebuffer(int target, int framebuffer) { public static void glDeleteFramebuffers(IntBuffer framebuffers) { org.lwjgl.opengl.GL30.glDeleteFramebuffers(framebuffers); if (Properties.VALIDATE.enabled) { - Context context = Context.currentContext(); - int pos = framebuffers.position(); - for (int i = 0; i < framebuffers.remaining(); i++) { - int framebuffer = framebuffers.get(pos + i); - if (framebuffer == 0) - continue; - FBO fbo = context.fbos.get(framebuffer); - if (fbo != null && fbo == context.currentFbo) { - context.currentFbo = context.defaultFbo; - } - context.fbos.remove(framebuffer); - } + deleteFramebuffers(framebuffers); } } public static void glDeleteFramebuffers(int framebuffer) { org.lwjgl.opengl.GL30.glDeleteFramebuffers(framebuffer); if (Properties.VALIDATE.enabled) { - if (framebuffer == 0) - return; - Context context = Context.currentContext(); - FBO fbo = context.fbos.get(framebuffer); - if (fbo != null && fbo == context.currentFbo) { - context.currentFbo = context.defaultFbo; - } - context.fbos.remove(framebuffer); + deleteFramebuffer(framebuffer); } } public static void glDeleteFramebuffers(int[] framebuffers) { org.lwjgl.opengl.GL30.glDeleteFramebuffers(framebuffers); + if (Properties.VALIDATE.enabled) { + deleteFramebuffers(framebuffers); + } + } + + public static void glGenRenderbuffers(IntBuffer renderbuffers) { + org.lwjgl.opengl.GL30.glGenRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = renderbuffers.position(); + for (int i = 0; i < renderbuffers.remaining(); i++) { + int handle = renderbuffers.get(pos + i); + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + } + } + + public static int glGenRenderbuffers() { + int handle = org.lwjgl.opengl.GL30.glGenRenderbuffers(); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + return handle; + } + + public static void glGenRenderbuffers(int[] renderbuffers) { + org.lwjgl.opengl.GL30.glGenRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < renderbuffers.length; i++) { + int handle = renderbuffers[i]; + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + } + } + + public static void glBindRenderbuffer(int target, int renderbuffer) { + if (Properties.VALIDATE.enabled && renderbuffer != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.renderbufferObjects.checkAlive(renderbuffer, "glBindRenderbuffer"); + } + org.lwjgl.opengl.GL30.glBindRenderbuffer(target, renderbuffer); + } + + public static void glDeleteRenderbuffers(IntBuffer renderbuffers) { + org.lwjgl.opengl.GL30.glDeleteRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + deleteRenderbuffers(renderbuffers); + } + } + + public static void glDeleteRenderbuffers(int renderbuffer) { + org.lwjgl.opengl.GL30.glDeleteRenderbuffers(renderbuffer); + if (Properties.VALIDATE.enabled) { + deleteRenderbuffer(renderbuffer); + } + } + + public static void glDeleteRenderbuffers(int[] renderbuffers) { + org.lwjgl.opengl.GL30.glDeleteRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + deleteRenderbuffers(renderbuffers); + } + } + + public static void glFramebufferTexture1D(int target, int attachment, int textarget, int texture, int level) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glFramebufferTexture1D"); + } + FBO fbo = (target == org.lwjgl.opengl.GL30.GL_READ_FRAMEBUFFER) ? ctx.currentReadFbo : ctx.currentDrawFbo; + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.GL30.glFramebufferTexture1D(target, attachment, textarget, texture, level); + } + + public static void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glFramebufferTexture2D"); + } + FBO fbo = (target == org.lwjgl.opengl.GL30.GL_READ_FRAMEBUFFER) ? ctx.currentReadFbo : ctx.currentDrawFbo; + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.GL30.glFramebufferTexture2D(target, attachment, textarget, texture, level); + } + + public static void glFramebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glFramebufferTexture3D"); + } + FBO fbo = (target == org.lwjgl.opengl.GL30.GL_READ_FRAMEBUFFER) ? ctx.currentReadFbo : ctx.currentDrawFbo; + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.GL30.glFramebufferTexture3D(target, attachment, textarget, texture, level, zoffset); + } + + public static void glFramebufferTextureLayer(int target, int attachment, int texture, int level, int layer) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glFramebufferTextureLayer"); + } + FBO fbo = (target == org.lwjgl.opengl.GL30.GL_READ_FRAMEBUFFER) ? ctx.currentReadFbo : ctx.currentDrawFbo; + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.GL30.glFramebufferTextureLayer(target, attachment, texture, level, layer); + } + + public static void glFramebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (renderbuffer != 0) { + ctx.shareGroup.renderbufferObjects.checkAlive(renderbuffer, "glFramebufferRenderbuffer"); + } + FBO fbo = (target == org.lwjgl.opengl.GL30.GL_READ_FRAMEBUFFER) ? ctx.currentReadFbo : ctx.currentDrawFbo; + if (fbo != null) { + fbo.attachRenderbuffer(attachment, renderbuffer); + } + } + org.lwjgl.opengl.GL30.glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer); + } + + public static void glBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { if (Properties.VALIDATE.enabled) { Context context = Context.currentContext(); - for (int i = 0; i < framebuffers.length; i++) { - int framebuffer = framebuffers[i]; - if (framebuffer == 0) - continue; - FBO fbo = context.fbos.get(framebuffer); - if (fbo != null && fbo == context.currentFbo) { - context.currentFbo = context.defaultFbo; + if (context.currentReadFbo != null) { + for (int tex : context.currentReadFbo.attachedTextures.values()) { + context.shareGroup.textureObjects.checkAlive(tex, "glBlitFramebuffer"); + } + for (int rb : context.currentReadFbo.attachedRenderbuffers.values()) { + context.shareGroup.renderbufferObjects.checkAlive(rb, "glBlitFramebuffer"); + } + } + if (context.currentDrawFbo != null) { + for (int tex : context.currentDrawFbo.attachedTextures.values()) { + context.shareGroup.textureObjects.checkAlive(tex, "glBlitFramebuffer"); + } + for (int rb : context.currentDrawFbo.attachedRenderbuffers.values()) { + context.shareGroup.renderbufferObjects.checkAlive(rb, "glBlitFramebuffer"); + } + } + } + org.lwjgl.opengl.GL30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + } + + public static int glCheckFramebufferStatus(int target) { + if (Properties.VALIDATE.enabled) { + Context context = Context.currentContext(); + FBO fbo = (target == org.lwjgl.opengl.GL30.GL_READ_FRAMEBUFFER) ? context.currentReadFbo : context.currentDrawFbo; + if (fbo != null) { + for (int tex : fbo.attachedTextures.values()) { + context.shareGroup.textureObjects.checkAlive(tex, "glCheckFramebufferStatus"); + } + for (int rb : fbo.attachedRenderbuffers.values()) { + context.shareGroup.renderbufferObjects.checkAlive(rb, "glCheckFramebufferStatus"); } - context.fbos.remove(framebuffer); } } + return org.lwjgl.opengl.GL30.glCheckFramebufferStatus(target); + } + + public static void glBindBufferBase(int target, int index, int buffer) { + if (Properties.VALIDATE.enabled && buffer != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glBindBufferBase"); + } + org.lwjgl.opengl.GL30.glBindBufferBase(target, index, buffer); + } + + public static void glBindBufferRange(int target, int index, int buffer, long offset, long size) { + if (Properties.VALIDATE.enabled && buffer != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glBindBufferRange"); + } + org.lwjgl.opengl.GL30.glBindBufferRange(target, index, buffer, offset, size); } } diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/GL31.java b/src/org/lwjglx/debug/org/lwjgl/opengl/GL31.java index d432c6f..c8a3de2 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/GL31.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/GL31.java @@ -86,4 +86,12 @@ public static void glDrawElementsInstanced(int mode, IntBuffer indices, int prim RT.draw(indices.remaining() * primcount); } + public static void glTexBuffer(int target, int internalformat, int buffer) { + if (Properties.VALIDATE.enabled && buffer != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glTexBuffer"); + } + org.lwjgl.opengl.GL31.glTexBuffer(target, internalformat, buffer); + } + } diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/GL43.java b/src/org/lwjglx/debug/org/lwjgl/opengl/GL43.java new file mode 100644 index 0000000..b07adc3 --- /dev/null +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/GL43.java @@ -0,0 +1,15 @@ +package org.lwjglx.debug.org.lwjgl.opengl; + +import org.lwjglx.debug.Properties; + +public class GL43 { + + public static void glTexBufferRange(int target, int internalformat, int buffer, long offset, long size) { + if (Properties.VALIDATE.enabled && buffer != 0) { + Context ctx = Context.currentContext(); + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glTexBufferRange"); + } + org.lwjgl.opengl.GL43.glTexBufferRange(target, internalformat, buffer, offset, size); + } + +} diff --git a/src/org/lwjglx/debug/org/lwjgl/opengl/GL45.java b/src/org/lwjglx/debug/org/lwjgl/opengl/GL45.java index 3243b1c..1ea66b9 100644 --- a/src/org/lwjglx/debug/org/lwjgl/opengl/GL45.java +++ b/src/org/lwjglx/debug/org/lwjgl/opengl/GL45.java @@ -14,8 +14,10 @@ public static void glCreateVertexArrays(IntBuffer arrays) { Context context = Context.currentContext(); int position = arrays.position(); for (int i = 0; i < arrays.remaining(); i++) { + int handle = arrays.get(position + i); VAO vao = new VAO(context.GL_MAX_VERTEX_ATTRIBS); - context.vaos.put(arrays.get(position + i), vao); + context.vaos.put(handle, vao); + context.vaoTracker.create(handle, vao); } } } @@ -26,24 +28,33 @@ public static int glCreateVertexArrays() { Context context = Context.currentContext(); VAO vao = new VAO(context.GL_MAX_VERTEX_ATTRIBS); context.vaos.put(index, vao); + context.vaoTracker.create(index, vao); } return index; } public static void glDisableVertexArrayAttrib(int vaobj, int index) { - org.lwjgl.opengl.GL45.glDisableVertexArrayAttrib(vaobj, index); if (Properties.VALIDATE.enabled) { Context context = Context.currentContext(); - context.vaos.get(vaobj).enabledVertexArrays[index] = false; + context.vaoTracker.checkAlive(vaobj, "glDisableVertexArrayAttrib"); + VAO vao = context.vaos.get(vaobj); + if (vao != null && index > -1 && index < vao.enabledVertexArrays.length) { + vao.enabledVertexArrays[index] = false; + } } + org.lwjgl.opengl.GL45.glDisableVertexArrayAttrib(vaobj, index); } public static void glEnableVertexArrayAttrib(int vaobj, int index) { - org.lwjgl.opengl.GL45.glEnableVertexArrayAttrib(vaobj, index); if (Properties.VALIDATE.enabled) { Context context = Context.currentContext(); - context.vaos.get(vaobj).enabledVertexArrays[index] = true; + context.vaoTracker.checkAlive(vaobj, "glEnableVertexArrayAttrib"); + VAO vao = context.vaos.get(vaobj); + if (vao != null && index > -1 && index < vao.enabledVertexArrays.length) { + vao.enabledVertexArrays[index] = true; + } } + org.lwjgl.opengl.GL45.glEnableVertexArrayAttrib(vaobj, index); } public static int glCreateFramebuffers() { @@ -52,6 +63,7 @@ public static int glCreateFramebuffers() { FBO fbo = new FBO(handle); Context ctx = Context.currentContext(); ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); } return handle; } @@ -64,6 +76,7 @@ public static void glCreateFramebuffers(int[] framebuffers) { int handle = framebuffers[i]; FBO fbo = new FBO(handle); ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); } } } @@ -77,8 +90,210 @@ public static void glCreateFramebuffers(IntBuffer framebuffers) { int handle = framebuffers.get(pos + i); FBO fbo = new FBO(handle); ctx.fbos.put(handle, fbo); + ctx.fboTracker.create(handle, fbo); + } + } + } + + public static int glCreateBuffers() { + int handle = org.lwjgl.opengl.GL45.glCreateBuffers(); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.bufferObjects.create(handle, new BufferObject()); + } + return handle; + } + + public static void glCreateBuffers(IntBuffer buffers) { + org.lwjgl.opengl.GL45.glCreateBuffers(buffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = buffers.position(); + for (int i = 0; i < buffers.remaining(); i++) { + ctx.shareGroup.bufferObjects.create(buffers.get(pos + i), new BufferObject()); + } + } + } + + public static void glCreateBuffers(int[] buffers) { + org.lwjgl.opengl.GL45.glCreateBuffers(buffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < buffers.length; i++) { + ctx.shareGroup.bufferObjects.create(buffers[i], new BufferObject()); + } + } + } + + public static int glCreateTextures(int target) { + int handle = org.lwjgl.opengl.GL45.glCreateTextures(target); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.textureObjects.create(handle, new TextureObject()); + } + return handle; + } + + public static void glCreateTextures(int target, IntBuffer textures) { + org.lwjgl.opengl.GL45.glCreateTextures(target, textures); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = textures.position(); + for (int i = 0; i < textures.remaining(); i++) { + ctx.shareGroup.textureObjects.create(textures.get(pos + i), new TextureObject()); + } + } + } + + public static void glCreateTextures(int target, int[] textures) { + org.lwjgl.opengl.GL45.glCreateTextures(target, textures); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < textures.length; i++) { + ctx.shareGroup.textureObjects.create(textures[i], new TextureObject()); + } + } + } + + public static int glCreateProgramPipelines() { + int handle = org.lwjgl.opengl.GL45.glCreateProgramPipelines(); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ProgramPipeline pp = new ProgramPipeline(); + ctx.programPipelines.put(handle, pp); + ctx.pipelineTracker.create(handle, pp); + } + return handle; + } + + public static void glCreateProgramPipelines(IntBuffer pipelines) { + org.lwjgl.opengl.GL45.glCreateProgramPipelines(pipelines); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = pipelines.position(); + for (int i = 0; i < pipelines.remaining(); i++) { + int handle = pipelines.get(pos + i); + ProgramPipeline pp = new ProgramPipeline(); + ctx.programPipelines.put(handle, pp); + ctx.pipelineTracker.create(handle, pp); + } + } + } + + public static void glCreateProgramPipelines(int[] pipelines) { + org.lwjgl.opengl.GL45.glCreateProgramPipelines(pipelines); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < pipelines.length; i++) { + int handle = pipelines[i]; + ProgramPipeline pp = new ProgramPipeline(); + ctx.programPipelines.put(handle, pp); + ctx.pipelineTracker.create(handle, pp); + } + } + } + + public static int glCreateRenderbuffers() { + int handle = org.lwjgl.opengl.GL45.glCreateRenderbuffers(); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + return handle; + } + + public static void glCreateRenderbuffers(IntBuffer renderbuffers) { + org.lwjgl.opengl.GL45.glCreateRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + int pos = renderbuffers.position(); + for (int i = 0; i < renderbuffers.remaining(); i++) { + int handle = renderbuffers.get(pos + i); + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + } + } + + public static void glCreateRenderbuffers(int[] renderbuffers) { + org.lwjgl.opengl.GL45.glCreateRenderbuffers(renderbuffers); + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + for (int i = 0; i < renderbuffers.length; i++) { + int handle = renderbuffers[i]; + ctx.shareGroup.renderbufferObjects.create(handle, new Context.RenderbufferObject()); + } + } + } + + public static void glNamedFramebufferTexture(int framebuffer, int attachment, int texture, int level) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.fboTracker.checkAlive(framebuffer, "glNamedFramebufferTexture"); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glNamedFramebufferTexture"); + } + Context.FBO fbo = ctx.fbos.get(framebuffer); + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.GL45.glNamedFramebufferTexture(framebuffer, attachment, texture, level); + } + + public static void glNamedFramebufferRenderbuffer(int framebuffer, int attachment, int renderbuffertarget, int renderbuffer) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.fboTracker.checkAlive(framebuffer, "glNamedFramebufferRenderbuffer"); + if (renderbuffer != 0) { + ctx.shareGroup.renderbufferObjects.checkAlive(renderbuffer, "glNamedFramebufferRenderbuffer"); + } + Context.FBO fbo = ctx.fbos.get(framebuffer); + if (fbo != null) { + fbo.attachRenderbuffer(attachment, renderbuffer); + } + } + org.lwjgl.opengl.GL45.glNamedFramebufferRenderbuffer(framebuffer, attachment, renderbuffertarget, renderbuffer); + } + + public static void glNamedFramebufferTextureLayer(int framebuffer, int attachment, int texture, int level, int layer) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + ctx.fboTracker.checkAlive(framebuffer, "glNamedFramebufferTextureLayer"); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glNamedFramebufferTextureLayer"); + } + Context.FBO fbo = ctx.fbos.get(framebuffer); + if (fbo != null) { + fbo.attachTexture(attachment, texture); + } + } + org.lwjgl.opengl.GL45.glNamedFramebufferTextureLayer(framebuffer, attachment, texture, level, layer); + } + + public static void glTextureBuffer(int texture, int internalformat, int buffer) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glTextureBuffer"); + } + if (buffer != 0) { + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glTextureBuffer"); + } + } + org.lwjgl.opengl.GL45.glTextureBuffer(texture, internalformat, buffer); + } + + public static void glTextureBufferRange(int texture, int internalformat, int buffer, long offset, long size) { + if (Properties.VALIDATE.enabled) { + Context ctx = Context.currentContext(); + if (texture != 0) { + ctx.shareGroup.textureObjects.checkAlive(texture, "glTextureBufferRange"); + } + if (buffer != 0) { + ctx.shareGroup.bufferObjects.checkAlive(buffer, "glTextureBufferRange"); } } + org.lwjgl.opengl.GL45.glTextureBufferRange(texture, internalformat, buffer, offset, size); } } diff --git a/test/test/DebugALIT.java b/test/test/DebugALIT.java index 770e180..d30ac4a 100644 --- a/test/test/DebugALIT.java +++ b/test/test/DebugALIT.java @@ -17,6 +17,7 @@ import org.lwjgl.openal.ALCCapabilities; import org.lwjgl.openal.ALCapabilities; import org.lwjgl.openal.EXTEfx; +import org.lwjglx.debug.Properties; public class DebugALIT { @@ -160,8 +161,22 @@ public void testContextLeakedResourcesAudit() { alGenBuffers(); alGenSources(); - IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alcDestroyContext(tempCtx)); - assertTrue(ex.getMessage().contains("leaked")); + // Default: warn only + alcDestroyContext(tempCtx); + + // With FAIL_ON_LEAKS: throws IllegalStateException + long tempCtx2 = alcCreateContext(device, (IntBuffer) null); + assertTrue(tempCtx2 != 0L); + alcMakeContextCurrent(tempCtx2); + alGenBuffers(); + + Properties.FAIL_ON_LEAKS.enable(); + try { + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alcDestroyContext(tempCtx2)); + assertTrue(ex.getMessage().contains("leaked")); + } finally { + Properties.FAIL_ON_LEAKS.enabled = false; + } } @Test diff --git a/test/test/DebugIT.java b/test/test/DebugIT.java index 318fcc4..19751d0 100644 --- a/test/test/DebugIT.java +++ b/test/test/DebugIT.java @@ -8,6 +8,7 @@ import static org.lwjgl.opengl.GL15.*; import static org.lwjgl.opengl.GL20.*; import static org.lwjgl.opengl.GL30.*; +import static org.lwjgl.opengl.GL31.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -49,7 +50,19 @@ public class DebugIT { "testNoVertexAttribPointerInCustomVAO", "testNoVertexAttribPointerInCustomVAOWithIndicesBuffer", "testBindVAOFromSharedContext", - "testBindFBOFromSharedContext" + "testBindFBOFromSharedContext", + "testVAODoubleFree", + "testVAOUseAfterFree", + "testFBODoubleFree", + "testFBOUseAfterFree", + "testRenderbufferDoubleFree", + "testRenderbufferUseAfterFree", + "testFramebufferTextureUseAfterFree", + "testFramebufferRenderbufferUseAfterFree", + "testFramebufferDetachOnZero", + "testSplitFramebufferTargets", + "testIndexedBufferBindings", + "testTextureBufferValidation" )); static { @@ -766,6 +779,311 @@ public void testGlErrorInMainMethod() { assertThrowsWithMessage(IllegalStateException.class, () -> main(new String[0]), Pattern.compile("glEnable produced error: 1280 \\(GL_INVALID_ENUM\\)")); } + @Test + public void testTextureDoubleFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int tex = glGenTextures(); + glDeleteTextures(tex); + assertThrowsWithMessage(IllegalStateException.class, () -> glDeleteTextures(tex), + Pattern.compile("glDeleteTextures: Texture " + tex + " already deleted \\(double-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testTextureUseAfterFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int tex = glGenTextures(); + glDeleteTextures(tex); + assertThrowsWithMessage(IllegalStateException.class, () -> glBindTexture(GL_TEXTURE_2D, tex), + Pattern.compile("glBindTexture: Texture " + tex + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testBufferDoubleFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int buf = glGenBuffers(); + glDeleteBuffers(buf); + assertThrowsWithMessage(IllegalStateException.class, () -> glDeleteBuffers(buf), + Pattern.compile("glDeleteBuffers: Buffer " + buf + " already deleted \\(double-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testBufferUseAfterFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int buf = glGenBuffers(); + glDeleteBuffers(buf); + assertThrowsWithMessage(IllegalStateException.class, () -> glBindBuffer(GL_ARRAY_BUFFER, buf), + Pattern.compile("glBindBuffer: Buffer " + buf + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testShaderDoubleFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int shader = glCreateShader(GL_VERTEX_SHADER); + glDeleteShader(shader); + assertThrowsWithMessage(IllegalStateException.class, () -> glDeleteShader(shader), + Pattern.compile("glDeleteShader: Shader " + shader + " already deleted \\(double-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testShaderUseAfterFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int shader = glCreateShader(GL_VERTEX_SHADER); + glDeleteShader(shader); + assertThrowsWithMessage(IllegalStateException.class, () -> glCompileShader(shader), + Pattern.compile("glCompileShader: Shader " + shader + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testProgramDoubleFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int prog = glCreateProgram(); + glDeleteProgram(prog); + assertThrowsWithMessage(IllegalStateException.class, () -> glDeleteProgram(prog), + Pattern.compile("glDeleteProgram: Program " + prog + " already deleted \\(double-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testProgramUseAfterFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int prog = glCreateProgram(); + glDeleteProgram(prog); + assertThrowsWithMessage(IllegalStateException.class, () -> glUseProgram(prog), + Pattern.compile("glUseProgram: Program " + prog + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testVAODoubleFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int vao = glGenVertexArrays(); + glDeleteVertexArrays(vao); + assertThrowsWithMessage(IllegalStateException.class, () -> glDeleteVertexArrays(vao), + Pattern.compile("glDeleteVertexArrays: VAO " + vao + " already deleted \\(double-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testVAOUseAfterFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int vao = glGenVertexArrays(); + glDeleteVertexArrays(vao); + assertThrowsWithMessage(IllegalStateException.class, () -> glBindVertexArray(vao), + Pattern.compile("glBindVertexArray: VAO " + vao + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testFBODoubleFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int fbo = glGenFramebuffers(); + glDeleteFramebuffers(fbo); + assertThrowsWithMessage(IllegalStateException.class, () -> glDeleteFramebuffers(fbo), + Pattern.compile("glDeleteFramebuffers: FBO " + fbo + " already deleted \\(double-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testFBOUseAfterFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int fbo = glGenFramebuffers(); + glDeleteFramebuffers(fbo); + assertThrowsWithMessage(IllegalStateException.class, () -> glBindFramebuffer(GL_FRAMEBUFFER, fbo), + Pattern.compile("glBindFramebuffer: FBO " + fbo + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testRenderbufferDoubleFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int rb = glGenRenderbuffers(); + glDeleteRenderbuffers(rb); + assertThrowsWithMessage(IllegalStateException.class, () -> glDeleteRenderbuffers(rb), + Pattern.compile("glDeleteRenderbuffers: Renderbuffer " + rb + " already deleted \\(double-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testRenderbufferUseAfterFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int rb = glGenRenderbuffers(); + glDeleteRenderbuffers(rb); + assertThrowsWithMessage(IllegalStateException.class, () -> glBindRenderbuffer(GL_RENDERBUFFER, rb), + Pattern.compile("glBindRenderbuffer: Renderbuffer " + rb + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testFramebufferTextureUseAfterFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int fbo = glGenFramebuffers(); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + int tex = glGenTextures(); + glDeleteTextures(tex); + assertThrowsWithMessage(IllegalStateException.class, () -> glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0), + Pattern.compile("glFramebufferTexture2D: Texture " + tex + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testFramebufferRenderbufferUseAfterFree() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int fbo = glGenFramebuffers(); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + int rb = glGenRenderbuffers(); + glDeleteRenderbuffers(rb); + assertThrowsWithMessage(IllegalStateException.class, () -> glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb), + Pattern.compile("glFramebufferRenderbuffer: Renderbuffer " + rb + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testFramebufferDetachOnZero() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int fbo = glGenFramebuffers(); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + int tex = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, (ByteBuffer) null); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + + // Detach with 0 + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + + // Deleting texture now should succeed and not be considered attached + glDeleteTextures(tex); + glDeleteFramebuffers(fbo); + } + + @Test + public void testSplitFramebufferTargets() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + int readFbo = glGenFramebuffers(); + int drawFbo = glGenFramebuffers(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, drawFbo); + + Context ctx = Context.currentContext(); + assertEquals(readFbo, ctx.currentReadFbo.handle); + assertEquals(drawFbo, ctx.currentDrawFbo.handle); + + int readTex = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, readTex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, (ByteBuffer) null); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, readTex, 0); + + int drawTex = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, drawTex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, (ByteBuffer) null); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, drawTex, 0); + + assertTrue(ctx.currentReadFbo.attachedTextures.containsKey(GL_COLOR_ATTACHMENT0)); + assertEquals(readTex, ctx.currentReadFbo.attachedTextures.get(GL_COLOR_ATTACHMENT0)); + assertTrue(ctx.currentDrawFbo.attachedTextures.containsKey(GL_COLOR_ATTACHMENT0)); + assertEquals(drawTex, ctx.currentDrawFbo.attachedTextures.get(GL_COLOR_ATTACHMENT0)); + + glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); + glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); + + glBlitFramebuffer(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + glDeleteTextures(readTex); + glDeleteTextures(drawTex); + glDeleteFramebuffers(readFbo); + glDeleteFramebuffers(drawFbo); + + assertEquals(0, ctx.currentReadFbo.handle); + assertEquals(0, ctx.currentDrawFbo.handle); + } + + @Test + public void testIndexedBufferBindings() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + + int buf = glGenBuffers(); + glBindBuffer(GL_UNIFORM_BUFFER, buf); + glBufferData(GL_UNIFORM_BUFFER, 64, GL_STATIC_DRAW); + + glBindBufferBase(GL_UNIFORM_BUFFER, 0, buf); + glBindBufferRange(GL_UNIFORM_BUFFER, 1, buf, 0, 32); + + glDeleteBuffers(buf); + + assertThrowsWithMessage(IllegalStateException.class, + () -> glBindBufferBase(GL_UNIFORM_BUFFER, 0, buf), + Pattern.compile("glBindBufferBase: Buffer " + buf + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + + assertThrowsWithMessage(IllegalStateException.class, + () -> glBindBufferRange(GL_UNIFORM_BUFFER, 1, buf, 0, 32), + Pattern.compile("glBindBufferRange: Buffer " + buf + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + } + + @Test + public void testTextureBufferValidation() { + window = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(window); + createCapabilities(); + + int buf = glGenBuffers(); + glBindBuffer(GL_TEXTURE_BUFFER, buf); + glBufferData(GL_TEXTURE_BUFFER, 64, GL_STATIC_DRAW); + + int tex = glGenTextures(); + glBindTexture(GL_TEXTURE_BUFFER, tex); + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, buf); + + glDeleteBuffers(buf); + + assertThrowsWithMessage(IllegalStateException.class, + () -> glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, buf), + Pattern.compile("glTexBuffer: Buffer " + buf + " has been deleted \\(use-after-free\\).*", Pattern.DOTALL)); + + glDeleteTextures(tex); + } + + @Test + public void testFailOnLeaks() { + Properties.FAIL_ON_LEAKS.enable(); + try { + long win = glfwCreateWindow(800, 600, "", 0L, 0L); + glfwMakeContextCurrent(win); + createCapabilities(); + glGenTextures(); + assertThrowsWithMessage(IllegalStateException.class, () -> glfwDestroyWindow(win), + Pattern.compile("ShareGroup destroyed with 1 un-deleted Texture\\(s\\).*", Pattern.DOTALL)); + } finally { + Properties.FAIL_ON_LEAKS.enabled = false; + } + } + public static void main(String[] args) { // Will be instrumented for checking JVM argument as command line argument From 5202236df11dc77d810ee5c8753c3972898d4ce2 Mon Sep 17 00:00:00 2001 From: Mike Duigou Date: Wed, 30 Sep 2026 16:35:12 -0700 Subject: [PATCH 3/3] OpenAL enhancements from testing --- README.md | 34 ++++- src/org/lwjglx/debug/Properties.java | 8 +- src/org/lwjglx/debug/openal/ALContext.java | 15 ++- src/org/lwjglx/debug/openal/ALDevice.java | 1 + src/org/lwjglx/debug/openal/ALObjects.java | 34 ++++- src/org/lwjglx/debug/openal/ALRT.java | 23 ++-- .../lwjglx/debug/org/lwjgl/openal/AL10.java | 54 ++++---- .../lwjglx/debug/org/lwjgl/openal/AL11.java | 28 ++++ .../lwjglx/debug/org/lwjgl/openal/ALC10.java | 24 ++-- test/test/DebugALIT.java | 121 ++++++++++++++++-- 10 files changed, 280 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 3af9bdd..a4fc8c5 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Because some errors in user programs can cause the JVM to crash without a meanin The following configuration properties are available to configure the library: - `validate` - Perform argument validation and check for errors on each GL/AL call (enabled by default, set via system property `-Dorg.lwjglx.VALIDATE` or via Agent argument `v` / `validate`) - `strict` - Strict validation mode; captures allocation and deletion stack traces for all tracked resources to provide detailed diagnostics on use-after-free, double-free, and leak detection (disabled by default, set via system property `-Dorg.lwjglx.STRICT` or via Agent argument `strict` or `validate=s`) -- `failonleaks` - Fail on leaks; throws an `IllegalStateException` during context teardown if any resources were leaked, instead of only logging warnings (disabled by default, set via system property `-Dorg.lwjglx.FAIL_ON_LEAKS` or via Agent argument `failonleaks`) +- `failonleaks` - Fail on leaks; throws an `IllegalStateException` during context teardown or device close if any resources were leaked, instead of only logging warnings (disabled by default, set via system property `-Dorg.lwjglx.FAIL_ON_LEAKS` or via Agent argument `failonleaks`) - `trace` - Generate a trace log (set via system property `-Dorg.lwjglx.TRACE` or via Agent argument `t`) - `exclude` - Exclude trace outputs for called methods matching a given GLOB pattern (set via Agent argument `e`) - `nothrow` - Do not throw a Java exception on any detected error but only log the error. Note that this may result in a JVM crash due to illegal arguments or native errors. (set via system property `-Dorg.lwjglx.NO_THROW` or via Agent argument `n`) @@ -55,7 +55,39 @@ Examples: * `java -javaagent:lwjglx-debug-1.1.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.1.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`) +### Dynamic Runtime Adjustment + +Properties can also be inspected and adjusted dynamically at runtime in Java code without restarting the JVM by modifying the fields on `org.lwjglx.debug.Properties`: + +```java +import org.lwjglx.debug.Properties; + +// Disable validation and synchronous error checking during performance-critical sections/benchmarks +Properties.VALIDATE.enabled = false; +try { + runAudioOrRenderBenchmark(); +} finally { + Properties.VALIDATE.enabled = true; +} + +// Selectively trace a specific subsystem or frame +Properties.TRACE.enabled = true; +renderProblematicFrame(); +Properties.TRACE.enabled = false; + +// Enforce strict leak failure on teardown for specific integration tests +Properties.FAIL_ON_LEAKS.enable(); +``` + +#### Caveats + +- **Bytecode Instrumentation vs. Runtime Checks:** Class transformation occurs at class-load time when the Java agent rewrites bytecode to route calls through proxy methods. Toggling flags such as `Properties.VALIDATE.enabled` or `Properties.TRACE.enabled` only toggles whether runtime checks, error queries (such as OpenAL `alGetError()`/`alcGetError()`), or logging are executed; it does not undo bytecode instrumentation or modify class-loading exclusion patterns (`exclude`). +- **Resource Tracking Continuity:** Disabling `VALIDATE` bypasses lifecycle tracking for objects created or destroyed during that interval. If resources (textures, buffers, sources) are allocated, bound, or deleted while validation is disabled, re-enabling validation later may cause missing-handle or false-positive use-after-free warnings. +- **Strict Mode Stack Traces:** Toggling `Properties.STRICT.enabled = true` mid-execution only records allocation stack traces for resources created *after* the flag was enabled. Resources created prior will display `(unknown)` for their creation site. +- **I/O Redirection:** `Properties.OUTPUT` initializes file writers and zip/gz archive streams during agent startup; changing output target files or archive streams mid-execution is not supported. + # Build 1. `./mvnw package` 2. see target/lwjglx-debug-1.1.0.jar + diff --git a/src/org/lwjglx/debug/Properties.java b/src/org/lwjglx/debug/Properties.java index 3b40950..406d377 100644 --- a/src/org/lwjglx/debug/Properties.java +++ b/src/org/lwjglx/debug/Properties.java @@ -25,8 +25,8 @@ of this software and associated documentation files (the "Software"), to deal public class Properties { public static class BooleanProperty { - public boolean enabled; - public boolean byDefault = true; + public volatile boolean enabled; + public volatile boolean byDefault = true; public void enable() { this.enabled = true; @@ -47,8 +47,8 @@ public void disableIfByDefault() { public static final BooleanProperty TRACE = getBooleanProperty("org.lwjglx.TRACE", false); public static final BooleanProperty NO_THROW_ON_ERROR = getBooleanProperty("org.lwjglx.NO_THROW", false); public static final BooleanProperty FAIL_ON_LEAKS = getBooleanProperty("org.lwjglx.FAIL_ON_LEAKS", false); - public static String OUTPUT = System.getProperty("org.lwjglx.OUTPUT", null); - public static long SLEEP = getLongProperty("org.lwjglx.SLEEP", 0L); + public static volatile String OUTPUT = System.getProperty("org.lwjglx.OUTPUT", null); + public static volatile long SLEEP = getLongProperty("org.lwjglx.SLEEP", 0L); private static BooleanProperty getBooleanProperty(String prop, boolean def) { String value = System.getProperty(prop); diff --git a/src/org/lwjglx/debug/openal/ALContext.java b/src/org/lwjglx/debug/openal/ALContext.java index 7322b9f..ecb420d 100644 --- a/src/org/lwjglx/debug/openal/ALContext.java +++ b/src/org/lwjglx/debug/openal/ALContext.java @@ -8,13 +8,13 @@ public class ALContext { private static final ConcurrentHashMap CONTEXTS = new ConcurrentHashMap<>(); - private static volatile ALContext currentContext; + private static final ThreadLocal CURRENT_CONTEXT = new ThreadLocal<>(); + private static volatile ALContext processContext; public final long handle; public final ALDevice device; public volatile ResourceState state = ResourceState.ALIVE; public final ConcurrentHashMap sources = new ConcurrentHashMap<>(); - public final ConcurrentHashMap buffers = new ConcurrentHashMap<>(); public final ConcurrentHashMap effects = new ConcurrentHashMap<>(); public final ConcurrentHashMap filters = new ConcurrentHashMap<>(); public final ConcurrentHashMap auxSlots = new ConcurrentHashMap<>(); @@ -42,10 +42,17 @@ public static java.util.Collection allContexts() { } public static ALContext currentContext() { - return currentContext; + ALContext ctx = CURRENT_CONTEXT.get(); + return ctx != null ? ctx : processContext; } public static void makeCurrent(ALContext context) { - currentContext = context; + if (context == null) { + CURRENT_CONTEXT.remove(); + processContext = null; + } else { + CURRENT_CONTEXT.set(context); + processContext = context; + } } } diff --git a/src/org/lwjglx/debug/openal/ALDevice.java b/src/org/lwjglx/debug/openal/ALDevice.java index 61b6301..fef8a08 100644 --- a/src/org/lwjglx/debug/openal/ALDevice.java +++ b/src/org/lwjglx/debug/openal/ALDevice.java @@ -13,6 +13,7 @@ public class ALDevice { public final long handle; public volatile ResourceState state = ResourceState.ALIVE; public final Set contexts = ConcurrentHashMap.newKeySet(); + public final ConcurrentHashMap buffers = new ConcurrentHashMap<>(); public final Throwable creationSite; private ALDevice(long handle) { diff --git a/src/org/lwjglx/debug/openal/ALObjects.java b/src/org/lwjglx/debug/openal/ALObjects.java index ad1f29a..f6e483f 100644 --- a/src/org/lwjglx/debug/openal/ALObjects.java +++ b/src/org/lwjglx/debug/openal/ALObjects.java @@ -10,15 +10,15 @@ public class ALObjects { public static class Buffer { public final int name; - public final ALContext owningContext; + public final ALDevice owningDevice; public volatile ResourceState state = ResourceState.ALIVE; public final Set attachedSources = ConcurrentHashMap.newKeySet(); public final Throwable creationSite; public volatile Throwable deletionSite; - public Buffer(int name, ALContext owningContext) { + public Buffer(int name, ALDevice owningDevice) { this.name = name; - this.owningContext = owningContext; + this.owningDevice = owningDevice; this.creationSite = Properties.STRICT.enabled ? new Throwable("Buffer created here") : null; } } @@ -28,6 +28,7 @@ public static class Source { public final ALContext owningContext; public volatile ResourceState state = ResourceState.ALIVE; public volatile Buffer attachedBuffer; + public final java.util.concurrent.ConcurrentHashMap queuedBuffers = new java.util.concurrent.ConcurrentHashMap<>(); public final Throwable creationSite; public volatile Throwable deletionSite; @@ -36,6 +37,33 @@ public Source(int name, ALContext owningContext) { this.owningContext = owningContext; this.creationSite = Properties.STRICT.enabled ? new Throwable("Source created here") : null; } + + public void queueBuffer(Buffer b) { + queuedBuffers.compute(b, (k, count) -> { + if (count == null) { + b.attachedSources.add(this); + return 1; + } + return count + 1; + }); + } + + public void unqueueBuffer(Buffer b) { + queuedBuffers.computeIfPresent(b, (k, count) -> { + if (count <= 1) { + b.attachedSources.remove(this); + return null; + } + return count - 1; + }); + } + + public void clearQueuedBuffers() { + for (Buffer b : queuedBuffers.keySet()) { + b.attachedSources.remove(this); + } + queuedBuffers.clear(); + } } public static class Effect { diff --git a/src/org/lwjglx/debug/openal/ALRT.java b/src/org/lwjglx/debug/openal/ALRT.java index e756f4b..19b4ce0 100644 --- a/src/org/lwjglx/debug/openal/ALRT.java +++ b/src/org/lwjglx/debug/openal/ALRT.java @@ -53,13 +53,14 @@ public static ALObjects.Source checkSource(ALContext ctx, int source, String met public static ALObjects.Buffer checkBuffer(ALContext ctx, int buffer, String methodName) { if (ctx == null || buffer == 0) return null; - ALObjects.Buffer b = ctx.buffers.get(buffer); + ALDevice dev = ctx.device; + ALObjects.Buffer b = dev != null ? dev.buffers.get(buffer) : null; if (b == null) { - ALObjects.Buffer other = findBufferInAnyContext(buffer); + ALObjects.Buffer other = findBufferInAnyDevice(buffer); if (other != null) { - RT.throwISEOrLogError(methodName + ": buffer " + buffer + " belongs to context 0x" - + Long.toHexString(other.owningContext.handle) + ", not the current context 0x" - + Long.toHexString(ctx.handle) + " (cross-context use)"); + RT.throwISEOrLogError(methodName + ": buffer " + buffer + " belongs to device 0x" + + Long.toHexString(other.owningDevice.handle) + ", not the current device 0x" + + Long.toHexString(dev != null ? dev.handle : 0) + " (cross-device use)"); return null; } RT.throwISEOrLogError(methodName + ": unknown buffer name " + buffer); @@ -147,9 +148,9 @@ private static ALObjects.Source findSourceInAnyContext(int source) { return null; } - private static ALObjects.Buffer findBufferInAnyContext(int buffer) { - for (ALContext c : ALContext.allContexts()) { - ALObjects.Buffer b = c.buffers.get(buffer); + private static ALObjects.Buffer findBufferInAnyDevice(int buffer) { + for (ALDevice d : ALDevice.allDevices()) { + ALObjects.Buffer b = d.buffers.get(buffer); if (b != null) return b; } return null; @@ -180,6 +181,9 @@ private static ALObjects.AuxiliaryEffectSlot findAuxSlotInAnyContext(int slot) { } public static void checkALError(String methodName) { + if (!Properties.VALIDATE.enabled) { + return; + } ALContext ctx = ALContext.currentContext(); if (ctx == null || ctx.state == ResourceState.DELETED) { return; @@ -198,6 +202,9 @@ public static void checkALError(String methodName) { } public static void checkALCError(long device, String methodName) { + if (!Properties.VALIDATE.enabled) { + return; + } int err = org.lwjgl.openal.ALC10.alcGetError(device); if (err != 0) { RT.throwISEOrLogError(methodName + " produced error: " + ALMetadata.alcErrorName(err)); diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java b/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java index 1dc417c..d93c27c 100644 --- a/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java +++ b/src/org/lwjglx/debug/org/lwjgl/openal/AL10.java @@ -20,8 +20,8 @@ public class AL10 { public static int alGenBuffers() { ALContext ctx = ALRT.checkContext("alGenBuffers"); int buffer = org.lwjgl.openal.AL10.alGenBuffers(); - if (ctx != null && buffer != 0) { - ctx.buffers.put(buffer, new ALObjects.Buffer(buffer, ctx)); + if (ctx != null && ctx.device != null && buffer != 0) { + ctx.device.buffers.put(buffer, new ALObjects.Buffer(buffer, ctx.device)); } return buffer; } @@ -30,11 +30,11 @@ public static void alGenBuffers(IntBuffer buffers) { ALContext ctx = ALRT.checkContext("alGenBuffers"); int pos = buffers.position(); org.lwjgl.openal.AL10.alGenBuffers(buffers); - if (ctx != null) { + if (ctx != null && ctx.device != null) { for (int i = pos; i < buffers.limit(); i++) { int b = buffers.get(i); if (b != 0) { - ctx.buffers.put(b, new ALObjects.Buffer(b, ctx)); + ctx.device.buffers.put(b, new ALObjects.Buffer(b, ctx.device)); } } } @@ -43,10 +43,10 @@ public static void alGenBuffers(IntBuffer buffers) { public static void alGenBuffers(int[] buffers) { ALContext ctx = ALRT.checkContext("alGenBuffers"); org.lwjgl.openal.AL10.alGenBuffers(buffers); - if (ctx != null) { + if (ctx != null && ctx.device != null) { for (int b : buffers) { if (b != 0) { - ctx.buffers.put(b, new ALObjects.Buffer(b, ctx)); + ctx.device.buffers.put(b, new ALObjects.Buffer(b, ctx.device)); } } } @@ -87,7 +87,8 @@ public static void alDeleteBuffers(int[] buffers) { } private static void deleteBufferCheck(ALContext ctx, int buffer) { - ALObjects.Buffer b = ctx.buffers.get(buffer); + if (ctx.device == null) return; + ALObjects.Buffer b = ctx.device.buffers.get(buffer); if (b == null) { RT.throwISEOrLogError("alDeleteBuffers: unknown buffer " + buffer); return; @@ -284,6 +285,7 @@ private static void deleteSourceCheck(ALContext ctx, int source) { s.attachedBuffer.attachedSources.remove(s); s.attachedBuffer = null; } + s.clearQueuedBuffers(); s.state = ResourceState.DELETED; if (Properties.STRICT.enabled) { s.deletionSite = new Throwable("Source deleted here"); @@ -305,12 +307,20 @@ public static void alSourcei(int source, int param, int value) { if (s.attachedBuffer != null) { s.attachedBuffer.attachedSources.remove(s); } + s.clearQueuedBuffers(); s.attachedBuffer = b; b.attachedSources.add(s); } - } else if (s != null && s.attachedBuffer != null) { - s.attachedBuffer.attachedSources.remove(s); - s.attachedBuffer = null; + } else if (s != null) { + if (s.attachedBuffer != null) { + s.attachedBuffer.attachedSources.remove(s); + s.attachedBuffer = null; + } + s.clearQueuedBuffers(); + } + } else if (param == org.lwjgl.openal.EXTEfx.AL_DIRECT_FILTER) { + if (value != 0) { + ALRT.checkFilter(ctx, value, "alSourcei(AL_DIRECT_FILTER)"); } } org.lwjgl.openal.AL10.alSourcei(source, param, value); @@ -529,7 +539,7 @@ public static void alSourceQueueBuffers(int source, int buffer) { ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourceQueueBuffers"); ALObjects.Buffer b = ALRT.checkBuffer(ctx, buffer, "alSourceQueueBuffers"); if (s != null && b != null) { - b.attachedSources.add(s); + s.queueBuffer(b); } org.lwjgl.openal.AL10.alSourceQueueBuffers(source, buffer); } @@ -542,7 +552,7 @@ public static void alSourceQueueBuffers(int source, IntBuffer buffers) { for (int i = pos; i < buffers.limit(); i++) { ALObjects.Buffer b = ALRT.checkBuffer(ctx, buffers.get(i), "alSourceQueueBuffers"); if (b != null) { - b.attachedSources.add(s); + s.queueBuffer(b); } } } @@ -556,7 +566,7 @@ public static void alSourceQueueBuffers(int source, int[] buffers) { for (int bufId : buffers) { ALObjects.Buffer b = ALRT.checkBuffer(ctx, bufId, "alSourceQueueBuffers"); if (b != null) { - b.attachedSources.add(s); + s.queueBuffer(b); } } } @@ -567,10 +577,10 @@ public static int alSourceUnqueueBuffers(int source) { ALContext ctx = ALRT.checkContext("alSourceUnqueueBuffers"); ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourceUnqueueBuffers"); int buffer = org.lwjgl.openal.AL10.alSourceUnqueueBuffers(source); - if (ctx != null && s != null && buffer != 0) { - ALObjects.Buffer b = ctx.buffers.get(buffer); + if (ctx != null && ctx.device != null && s != null && buffer != 0) { + ALObjects.Buffer b = ctx.device.buffers.get(buffer); if (b != null) { - b.attachedSources.remove(s); + s.unqueueBuffer(b); } } return buffer; @@ -581,12 +591,12 @@ public static void alSourceUnqueueBuffers(int source, IntBuffer buffers) { ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourceUnqueueBuffers"); int pos = buffers.position(); org.lwjgl.openal.AL10.alSourceUnqueueBuffers(source, buffers); - if (ctx != null && s != null) { + if (ctx != null && ctx.device != null && s != null) { for (int i = pos; i < buffers.limit(); i++) { int bufId = buffers.get(i); - ALObjects.Buffer b = ctx.buffers.get(bufId); + ALObjects.Buffer b = ctx.device.buffers.get(bufId); if (b != null) { - b.attachedSources.remove(s); + s.unqueueBuffer(b); } } } @@ -596,11 +606,11 @@ public static void alSourceUnqueueBuffers(int source, int[] buffers) { ALContext ctx = ALRT.checkContext("alSourceUnqueueBuffers"); ALObjects.Source s = ALRT.checkSource(ctx, source, "alSourceUnqueueBuffers"); org.lwjgl.openal.AL10.alSourceUnqueueBuffers(source, buffers); - if (ctx != null && s != null) { + if (ctx != null && ctx.device != null && s != null) { for (int bufId : buffers) { - ALObjects.Buffer b = ctx.buffers.get(bufId); + ALObjects.Buffer b = ctx.device.buffers.get(bufId); if (b != null) { - b.attachedSources.remove(s); + s.unqueueBuffer(b); } } } diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/AL11.java b/src/org/lwjglx/debug/org/lwjgl/openal/AL11.java index b15cadd..eb95022 100644 --- a/src/org/lwjglx/debug/org/lwjgl/openal/AL11.java +++ b/src/org/lwjglx/debug/org/lwjgl/openal/AL11.java @@ -50,6 +50,14 @@ public static void alGetListeneriv(int param, int[] values) { public static void alSource3i(int source, int param, int v1, int v2, int v3) { ALContext ctx = ALRT.checkContext("alSource3i"); ALRT.checkSource(ctx, source, "alSource3i"); + if (param == org.lwjgl.openal.EXTEfx.AL_AUXILIARY_SEND_FILTER) { + if (v1 != 0) { + ALRT.checkAuxSlot(ctx, v1, "alSource3i(AL_AUXILIARY_SEND_FILTER:slot)"); + } + if (v3 != 0) { + ALRT.checkFilter(ctx, v3, "alSource3i(AL_AUXILIARY_SEND_FILTER:filter)"); + } + } org.lwjgl.openal.AL11.alSource3i(source, param, v1, v2, v3); } @@ -68,12 +76,32 @@ public static void alGetSource3i(int source, int param, int[] v1, int[] v2, int[ public static void alSourceiv(int source, int param, IntBuffer values) { ALContext ctx = ALRT.checkContext("alSourceiv"); ALRT.checkSource(ctx, source, "alSourceiv"); + if (param == org.lwjgl.openal.EXTEfx.AL_AUXILIARY_SEND_FILTER && values != null && values.remaining() >= 3) { + int slot = values.get(values.position()); + int filter = values.get(values.position() + 2); + if (slot != 0) { + ALRT.checkAuxSlot(ctx, slot, "alSourceiv(AL_AUXILIARY_SEND_FILTER:slot)"); + } + if (filter != 0) { + ALRT.checkFilter(ctx, filter, "alSourceiv(AL_AUXILIARY_SEND_FILTER:filter)"); + } + } org.lwjgl.openal.AL11.alSourceiv(source, param, values); } public static void alSourceiv(int source, int param, int[] values) { ALContext ctx = ALRT.checkContext("alSourceiv"); ALRT.checkSource(ctx, source, "alSourceiv"); + if (param == org.lwjgl.openal.EXTEfx.AL_AUXILIARY_SEND_FILTER && values != null && values.length >= 3) { + int slot = values[0]; + int filter = values[2]; + if (slot != 0) { + ALRT.checkAuxSlot(ctx, slot, "alSourceiv(AL_AUXILIARY_SEND_FILTER:slot)"); + } + if (filter != 0) { + ALRT.checkFilter(ctx, filter, "alSourceiv(AL_AUXILIARY_SEND_FILTER:filter)"); + } + } org.lwjgl.openal.AL11.alSourceiv(source, param, values); } diff --git a/src/org/lwjglx/debug/org/lwjgl/openal/ALC10.java b/src/org/lwjglx/debug/org/lwjgl/openal/ALC10.java index 31d35c6..d471bad 100644 --- a/src/org/lwjglx/debug/org/lwjgl/openal/ALC10.java +++ b/src/org/lwjglx/debug/org/lwjgl/openal/ALC10.java @@ -43,6 +43,20 @@ public static boolean alcCloseDevice(long device) { + " while context 0x" + Long.toHexString(ctx.handle) + " is still alive"); } } + boolean hasLeaks = false; + int liveBuffers = 0; + for (ALObjects.Buffer b : dev.buffers.values()) { + if (b.state == ResourceState.ALIVE) { + liveBuffers++; + } + } + if (liveBuffers > 0) { + hasLeaks = true; + Log.warn("OpenAL device 0x" + Long.toHexString(device) + " closed with " + liveBuffers + " un-deleted buffer(s)"); + } + if (hasLeaks && Properties.FAIL_ON_LEAKS.enabled) { + RT.throwISEOrLogError("OpenAL device 0x" + Long.toHexString(device) + " closed with leaked resources"); + } } boolean res = org.lwjgl.openal.ALC10.alcCloseDevice(device); if (res && dev != null) { @@ -124,16 +138,6 @@ public static void alcDestroyContext(long context) { try { boolean hasLeaks = false; - int liveBuffers = 0; - for (ALObjects.Buffer b : ctx.buffers.values()) { - if (b.state == ResourceState.ALIVE) { - liveBuffers++; - } - } - if (liveBuffers > 0) { - hasLeaks = true; - Log.warn("OpenAL context 0x" + Long.toHexString(context) + " destroyed with " + liveBuffers + " un-deleted buffer(s)"); - } int liveSources = 0; for (ALObjects.Source s : ctx.sources.values()) { diff --git a/test/test/DebugALIT.java b/test/test/DebugALIT.java index d30ac4a..ecfde87 100644 --- a/test/test/DebugALIT.java +++ b/test/test/DebugALIT.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.*; import static org.lwjgl.openal.AL10.*; +import static org.lwjgl.openal.AL11.*; import static org.lwjgl.openal.ALC10.*; import java.nio.ByteBuffer; @@ -157,8 +158,7 @@ public void testContextLeakedResourcesAudit() { assertTrue(tempCtx != 0L); alcMakeContextCurrent(tempCtx); - // Leak a buffer and a source - alGenBuffers(); + // Leak a source alGenSources(); // Default: warn only @@ -168,7 +168,7 @@ public void testContextLeakedResourcesAudit() { long tempCtx2 = alcCreateContext(device, (IntBuffer) null); assertTrue(tempCtx2 != 0L); alcMakeContextCurrent(tempCtx2); - alGenBuffers(); + alGenSources(); Properties.FAIL_ON_LEAKS.enable(); try { @@ -247,26 +247,116 @@ public void testCrossContextObjectUsage() { long ctx2 = alcCreateContext(device, (IntBuffer) null); assertTrue(ctx2 != 0L); - // Create buffer under current context (this.context) int buf = alGenBuffers(); assertTrue(buf > 0); + int src = alGenSources(); + assertTrue(src > 0); - // Switch to ctx2 alcMakeContextCurrent(ctx2); - - // Try to use buffer from context 1 while context 2 is current ByteBuffer data = BufferUtils.createByteBuffer(16); + alBufferData(buf, AL_FORMAT_MONO16, data, 44100); + IllegalStateException ex = assertThrows(IllegalStateException.class, - () -> alBufferData(buf, AL_FORMAT_MONO16, data, 44100)); + () -> alSourcei(src, AL_BUFFER, buf)); assertTrue(ex.getMessage().contains("cross-context use")); - // Switch back to context 1, delete buffer, then destroy ctx2 + long dev2 = alcOpenDevice((ByteBuffer) null); + if (dev2 != 0L) { + try { + long ctx3 = alcCreateContext(dev2, (IntBuffer) null); + if (ctx3 != 0L) { + try { + alcMakeContextCurrent(ctx3); + IllegalStateException ex2 = assertThrows(IllegalStateException.class, + () -> alBufferData(buf, AL_FORMAT_MONO16, data, 44100)); + assertTrue(ex2.getMessage().contains("cross-device use")); + } finally { + alcDestroyContext(ctx3); + } + } + } finally { + alcCloseDevice(dev2); + } + } + alcMakeContextCurrent(context); + alDeleteSources(src); alDeleteBuffers(buf); - alcDestroyContext(ctx2); } + @Test + public void testDeviceBufferLeakAudit() { + long tempDev = alcOpenDevice((ByteBuffer) null); + assumeTrue(tempDev != 0L); + long tempCtx = alcCreateContext(tempDev, (IntBuffer) null); + assumeTrue(tempCtx != 0L); + alcMakeContextCurrent(tempCtx); + + int buf = alGenBuffers(); + assertTrue(buf > 0); + + alcDestroyContext(tempCtx); + + Properties.FAIL_ON_LEAKS.enable(); + try { + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alcCloseDevice(tempDev)); + assertTrue(ex.getMessage().contains("leaked")); + } finally { + Properties.FAIL_ON_LEAKS.enabled = false; + alcCloseDevice(tempDev); + alcMakeContextCurrent(context); + } + } + + @Test + public void testMultiQueuedBuffers() { + int src = alGenSources(); + int buf = alGenBuffers(); + ByteBuffer data = BufferUtils.createByteBuffer(16); + alBufferData(buf, AL_FORMAT_MONO16, data, 44100); + + alSourceQueueBuffers(src, buf); + alSourceQueueBuffers(src, buf); + + alSourcePlay(src); + alSourceStop(src); + + int unqueued = alSourceUnqueueBuffers(src); + assertEquals(buf, unqueued); + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> alDeleteBuffers(buf)); + assertTrue(ex.getMessage().contains("still attached")); + + unqueued = alSourceUnqueueBuffers(src); + assertEquals(buf, unqueued); + + alDeleteBuffers(buf); + alDeleteSources(src); + } + + @Test + public void testEfxFilterValidation() { + ALCapabilities caps = AL.getCapabilities(); + assumeTrue(caps.ALC_EXT_EFX, "ALC_EXT_EFX extension not supported"); + + int filter = EXTEfx.alGenFilters(); + EXTEfx.alDeleteFilters(filter); + + int src = alGenSources(); + IllegalStateException ex = assertThrows(IllegalStateException.class, + () -> alSourcei(src, EXTEfx.AL_DIRECT_FILTER, filter)); + assertTrue(ex.getMessage().contains("use-after-free")); + + int slot = EXTEfx.alGenAuxiliaryEffectSlots(); + IllegalStateException ex2 = assertThrows(IllegalStateException.class, + () -> alSource3i(src, EXTEfx.AL_AUXILIARY_SEND_FILTER, slot, 0, filter)); + assertTrue(ex2.getMessage().contains("use-after-free")); + + EXTEfx.alDeleteAuxiliaryEffectSlots(slot); + alDeleteSources(src); + } + @Test public void testAlcDestroyWithOpenDevice() { long tempDev = alcOpenDevice((ByteBuffer) null); @@ -301,4 +391,15 @@ public void testAlMetadataEnumNaming() { assertEquals("AL_INVALID_NAME", org.lwjglx.debug.openal.ALMetadata.enumName(0xA001)); assertEquals("AL_INVALID_NAME", org.lwjglx.debug.openal.ALMetadata.alErrorName(0xA001)); } + + @Test + public void testValidateToggleSuppressesAlError() { + Properties.VALIDATE.enabled = false; + try { + org.lwjglx.debug.openal.ALRT.checkALError("testMethod"); + org.lwjglx.debug.openal.ALRT.checkALCError(device, "testMethod"); + } finally { + Properties.VALIDATE.enabled = true; + } + } }