org.lwjgl.openal.ALC Maven / Gradle / Ivy
Show all versions of lwjgl-openal Show documentation
/*
* Copyright LWJGL. All rights reserved.
* License terms: https://www.lwjgl.org/license
*/
package org.lwjgl.openal;
import org.jspecify.annotations.*;
import org.lwjgl.*;
import org.lwjgl.system.*;
import java.nio.*;
import java.util.*;
import java.util.function.*;
import static org.lwjgl.openal.ALC10.*;
import static org.lwjgl.system.APIUtil.*;
import static org.lwjgl.system.JNI.*;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*;
/**
* This class must be used before any OpenAL function is called. It has the following responsibilities:
*
* - Loads the OpenAL native library into the JVM process.
* - Creates instances of {@link ALCCapabilities} classes. An {@code ALCCapabilities} instance contains flags for functionality that is available for an
* OpenAL device. Internally, it also contains function pointers that are only valid for that specific OpenAL device.
*
*
* Library lifecycle
* The OpenAL library is loaded automatically when this class is initialized. Set the {@link Configuration#OPENAL_EXPLICIT_INIT} option to override this
* behavior. Manual loading/unloading can be achieved with the {@link #create} and {@link #destroy} functions. The name of the library loaded can be overridden
* with the {@link Configuration#OPENAL_LIBRARY_NAME} option.
*
* ALCCapabilities creation
* Instances of {@code ALCCapabilities} can be created with the {@link #createCapabilities} method. Calling this method is expensive, so
* {@code ALCCapabilities} instances should be cached in user code.
*
* @see AL
*/
public final class ALC {
private static @Nullable FunctionProviderLocal functionProvider;
private static @Nullable ALCCapabilities router;
private static final ThreadLocal capabilitiesTLS = new ThreadLocal<>();
private static @Nullable ICD icd;
static {
if (!Configuration.OPENAL_EXPLICIT_INIT.get(false)) {
create();
}
}
private ALC() {}
/** Loads the OpenAL native library, using the default library name. */
public static void create() {
String libName;
switch (Platform.get()) {
case FREEBSD:
case LINUX:
case MACOSX:
libName = "openal";
break;
case WINDOWS:
libName = "OpenAL";
break;
default:
throw new IllegalStateException();
}
create(Configuration.OPENAL_LIBRARY_NAME.get(Platform.mapLibraryNameBundled(libName)));
}
private static class SharedLibraryAL extends SharedLibrary.Delegate implements FunctionProviderLocal {
private final long alcGetProcAddress = getFunctionAddress("alcGetProcAddress");
protected SharedLibraryAL(SharedLibrary library) {
super(library);
if (alcGetProcAddress == NULL) {
throw new RuntimeException("A core ALC function is missing. Make sure that the OpenAL library has been loaded correctly.");
}
}
@Override
public long getFunctionAddress(ByteBuffer functionName) {
long address = library.getFunctionAddress(functionName);
if (address == NULL && Checks.DEBUG_FUNCTIONS) {
apiLogMissing("ALC core", functionName);
}
return address;
}
@Override
public long getFunctionAddress(long handle, ByteBuffer functionName) {
long address = invokePPP(handle, memAddress(functionName), alcGetProcAddress);
if (address == NULL) {
address = library.getFunctionAddress(functionName);
}
if (address == NULL && Checks.DEBUG_FUNCTIONS) {
apiLogMissing("ALC", functionName);
}
return address;
}
}
/**
* Loads the OpenAL native library, using the specified library name.
*
* @param libName the native library name
*/
public static void create(String libName) {
SharedLibrary OPENAL = Library.loadNative(ALC.class, "org.lwjgl.openal", libName, true);
try {
create(new SharedLibraryAL(OPENAL));
} catch (RuntimeException e) {
OPENAL.free();
throw e;
}
}
/**
* Initializes ALC with the specified {@link FunctionProviderLocal}. This method can be used to implement custom ALC library loading.
*
* @param functionProvider the provider of ALC function addresses
*/
public static void create(FunctionProviderLocal functionProvider) {
if (ALC.functionProvider != null) {
throw new IllegalStateException("ALC has already been created.");
}
ALC.functionProvider = functionProvider;
router = createCapabilities(NULL);
AL.init();
}
/** Unloads the OpenAL native library. */
public static void destroy() {
if (functionProvider == null) {
return;
}
AL.destroy();
router = null;
if (functionProvider instanceof NativeResource) {
((NativeResource)functionProvider).free();
}
functionProvider = null;
}
static T check(@Nullable T t) {
if (t == null) {
throw new IllegalStateException("OpenAL library has not been loaded.");
}
return t;
}
/** Returns the {@link FunctionProviderLocal} for the OpenAL native library. */
public static FunctionProviderLocal getFunctionProvider() {
return check(ALC.functionProvider);
}
/**
* Sets the specified {@link ALCCapabilities} for the current thread.
*
* Any ALC functions called in the current thread will use the specified {@code ALCCapabilities}.
*
* @param caps the {@link ALCCapabilities} to make current, or null
*/
public static void setCapabilities(@Nullable ALCCapabilities caps) {
capabilitiesTLS.set(caps);
if (icd == null) {
icd = new ICDStatic();
}
icd.set(caps);
}
/**
* Returns the {@link ALCCapabilities} for the current thread.
*
* @throws IllegalStateException if OpenAL has not been loaded.
*/
public static ALCCapabilities getCapabilities() {
ALCCapabilities caps = capabilitiesTLS.get();
if (caps == null) {
caps = router;
}
return checkCapabilities(caps);
}
private static ALCCapabilities checkCapabilities(@Nullable ALCCapabilities caps) {
if (caps == null) {
throw new IllegalStateException("No ALCCapabilities instance set");
}
return caps;
}
/**
* Creates a new {@link ALCCapabilities} instance for the specified OpenAL device.
*
* This method calls {@link #setCapabilities} with the new instance before returning.
*
* @param device the {@code ALCdevice} for which to create the capabilities instance
*
* @return the {@code ALCCapabilities} instance
*/
public static ALCCapabilities createCapabilities(long device) {
return createCapabilities(device, null);
}
/**
* Creates a new {@link ALCCapabilities} instance for the specified OpenAL device.
*
* This method calls {@link #setCapabilities} with the new instance before returning.
*
* @param device the {@code ALCdevice} for which to create the capabilities instance
* @param bufferFactory a function that allocates a {@link PointerBuffer} given a size. The buffer must be filled with zeroes. If {@code null}, LWJGL will
* allocate a GC-managed buffer internally.
*
* @return the {@code ALCCapabilities} instance
*/
public static ALCCapabilities createCapabilities(long device, @Nullable IntFunction bufferFactory) {
FunctionProviderLocal functionProvider = getFunctionProvider();
// We don't have an ALCCapabilities instance when this method is called
// so we have to use the native bindings directly.
long GetIntegerv = functionProvider.getFunctionAddress("alcGetIntegerv");
long GetString = functionProvider.getFunctionAddress("alcGetString");
long IsExtensionPresent = functionProvider.getFunctionAddress("alcIsExtensionPresent");
if (GetIntegerv == NULL || GetString == NULL || IsExtensionPresent == NULL) {
throw new IllegalStateException("Core ALC functions could not be found. Make sure that OpenAL has been loaded.");
}
int majorVersion;
int minorVersion;
try (MemoryStack stack = stackPush()) {
IntBuffer version = stack.mallocInt(1);
invokePPV(device, ALC_MAJOR_VERSION, 1, memAddress(version), GetIntegerv);
majorVersion = version.get(0);
invokePPV(device, ALC_MINOR_VERSION, 1, memAddress(version), GetIntegerv);
minorVersion = version.get(0);
}
int[][] ALC_VERSIONS = {
{0, 1}, // ALC 1
};
Set supportedExtensions = new HashSet<>(16);
for (int major = 1; major <= ALC_VERSIONS.length; major++) {
int[] minors = ALC_VERSIONS[major - 1];
for (int minor : minors) {
if (major < majorVersion || (major == majorVersion && minor <= minorVersion)) {
supportedExtensions.add("OpenALC" + major + minor);
}
}
}
// Parse EXTENSIONS string
String extensionsString = memASCIISafe(invokePP(device, ALC_EXTENSIONS, GetString));
if (extensionsString != null) {
StringTokenizer tokenizer = new StringTokenizer(extensionsString);
while (tokenizer.hasMoreTokens()) {
String extName = tokenizer.nextToken();
try (MemoryStack stack = stackPush()) {
if (invokePPZ(device, memAddress(stack.ASCII(extName, true)), IsExtensionPresent)) {
supportedExtensions.add(extName);
}
}
}
}
apiFilterExtensions(supportedExtensions, Configuration.OPENAL_EXTENSION_FILTER);
ALCCapabilities caps = new ALCCapabilities(functionProvider, device, supportedExtensions, bufferFactory == null ? BufferUtils::createPointerBuffer : bufferFactory);
if (device != NULL) {
setCapabilities(caps);
}
return caps;
}
static ALCCapabilities getICD() {
ALCCapabilities caps = icd == null ? null : icd.get();
if (caps == null) {
caps = router;
}
return check(caps);
}
/** Function pointer provider. */
private interface ICD {
default void set(@Nullable ALCCapabilities caps) {}
@Nullable ALCCapabilities get();
}
/**
* Write-once {@link ICD}.
*
* This is the default implementation that skips the thread/process lookup. When a new ALCCapabilities is set, we compare it to the write-once
* capabilities. If different function pointers are found, we fall back to the expensive lookup.
*/
private static class ICDStatic implements ICD {
private static @Nullable ALCCapabilities tempCaps;
@SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
@Override
public void set(@Nullable ALCCapabilities caps) {
if (tempCaps == null) {
tempCaps = caps;
} else if (caps != null && caps != tempCaps && ThreadLocalUtil.areCapabilitiesDifferent(tempCaps.addresses, caps.addresses)) {
apiLog("[WARNING] Incompatible context detected. Falling back to thread/process lookup for AL contexts.");
icd = ALC::getCapabilities; // fall back to thread/process lookup
}
}
@Override
public ALCCapabilities get() {
return WriteOnce.caps;
}
private static final class WriteOnce {
// This will be initialized the first time get() above is called
static final ALCCapabilities caps;
static {
ALCCapabilities tempCaps = ICDStatic.tempCaps;
if (tempCaps == null) {
throw new IllegalStateException("No ALCCapabilities instance has been set");
}
caps = tempCaps;
}
}
}
}