/** * Copyright 2014 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package jogamp.opengl; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; import com.jogamp.opengl.*; import com.jogamp.common.ExceptionUtils; import com.jogamp.common.nio.Buffers; import com.jogamp.common.util.IntObjectHashMap; import com.jogamp.common.util.PropertyAccess; /** * Tracking of {@link GLBufferStorage} instances via GL API callbacks. *
* See {@link GLBufferStorage} for generic details. *
** Buffer storage is created via *
* Buffers storage is disposed via *
* Implementation throws a {@link GLException} in all construction methods as listed below,
* if GL-function constraints are not met. createBufferStorage
also throws an exception
* if the native GL-function fails.
*
* Buffer mapping methods like {@link GL#mapBuffer(int, int)} ..., * require knowledge of the buffer's storage size as determined via it's creation with * {@link GL#glBufferData(int, long, java.nio.Buffer, int)} .... *
** Hence we track the OpenGL buffer's {@link GLBufferStorage} to be able to * access the buffer's storage size. The tracked {@link GLBufferStorage} instances * also allow users to conveniently access details of their created and maybe mapped buffer storage. *
** The {@link GLBufferObjectTracker} and it's tracked {@link GLBufferStorage} instances * maybe shared across multiple OpenGL context, hence this class is thread safe and employs synchronization. *
** Implementation requires and utilizes a local {@link GLBufferStateTracker} * to resolve the actual buffer-name bound to the given target. *
** Note: This tracker requires to be notified about all OpenGL buffer storage operations, * as well as the local {@link GLBufferStateTracker} to be notified about all * OpenGL buffer binding operations. * Hence buffer storage cannot be accessed properly if managed via native code. *
*/ public class GLBufferObjectTracker { protected static final boolean DEBUG; static { Debug.initSingleton(); DEBUG = PropertyAccess.isPropertyDefined("jogl.debug.GLBufferObjectTracker", true); } static final class GLBufferStorageImpl extends GLBufferStorage { GLBufferStorageImpl(final int name, final long size, final int mutableUsage, final int immutableFlags) { super(name, size, mutableUsage, immutableFlags); } protected final void reset(final long size, final int mutableUsage, final int immutableFlags) { super.reset(size, mutableUsage, immutableFlags); } protected final void setMappedBuffer(final ByteBuffer buffer) { super.setMappedBuffer(buffer); } } /** * Map from buffer names to GLBufferObject. */ private final IntObjectHashMap bufferName2StorageMap; public GLBufferObjectTracker() { bufferName2StorageMap = new IntObjectHashMap(); bufferName2StorageMap.setKeyNotFoundValue(null); } public static interface CreateStorageDispatch { void create(final int targetOrBufferName, final long size, final Buffer data, final int mutableUsageOrImmutableFlags); } /** * Must be called when [re]creating the GL buffer object via {@link GL#glBufferData(int, long, java.nio.Buffer, int)} * and {@link GL4#glBufferStorage(int, long, Buffer, int)}, * i.e. implies destruction of the buffer. * * @param bufferStateTracker * @param caller * @param target * @param size * @param mutableUsageglBufferData
, glNamedBufferData
usage
* @param immutableFlags glBufferStorage
flags
* @throws GLException if buffer is not bound to target
* @throws GLException if size is less-or-eqaul zero for glBufferStorage
, or size is less-than zero otherwise
* @throws GLException if a native GL-Error occurs
*/
public synchronized final void createBufferStorage(final GLBufferStateTracker bufferStateTracker, final GL caller,
final int target, final long size, final Buffer data, final int mutableUsage, final int immutableFlags,
final CreateStorageDispatch dispatch) throws GLException {
final int glerrPre = caller.glGetError(); // clear
if (DEBUG && GL.GL_NO_ERROR != glerrPre) {
System.err.printf("%s.%s glerr-pre 0x%X%n", msgClazzName, msgCreateBound, glerrPre);
}
final int bufferName = bufferStateTracker.getBoundBufferObject(target, caller);
if ( 0 == bufferName ) {
throw new GLException(String.format("%s: Buffer for target 0x%X not bound", GL_INVALID_OPERATION, target));
}
final boolean mutableBuffer = 0 != mutableUsage;
final boolean invalidSize = ( mutableBuffer && 0 > size ) // glBufferData, glNamedBufferData
|| ( !mutableBuffer && 0 >= size ); // glBufferStorage
if( invalidSize ) {
throw new GLException(String.format("%s: Invalid size %d for buffer %d on target 0x%X", GL_INVALID_VALUE, size, bufferName, target));
}
dispatch.create(target, size, data, mutableBuffer ? mutableUsage : immutableFlags);
final int glerrPost = caller.glGetError(); // be safe, catch failure!
if(GL.GL_NO_ERROR != glerrPost) {
throw new GLException(String.format("GL-Error 0x%X while creating %s storage for target 0x%X -> buffer %d of size %d with data %s",
glerrPost, mutableBuffer ? "mutable" : "immutable", target, bufferName, size, data));
}
final GLBufferStorageImpl objOld = (GLBufferStorageImpl) bufferName2StorageMap.get(bufferName);
if( null != objOld ) {
objOld.reset(size, mutableUsage, immutableFlags);
if (DEBUG) {
System.err.printf("%s.%s target: 0x%X -> reset %d: %s%n", msgClazzName, msgCreateBound, target, bufferName, objOld);
}
} else {
final GLBufferStorageImpl objNew = new GLBufferStorageImpl(bufferName, size, mutableUsage, immutableFlags);
bufferName2StorageMap.put(bufferName, objNew);
if (DEBUG) {
System.err.printf("%s.%s target: 0x%X -> new %d: %s%n", msgClazzName, msgCreateBound, target, bufferName, objNew);
}
}
}
/**
* Must be called when [re]creating the GL buffer object via {@link GL2#glNamedBufferData(int, long, java.nio.Buffer, int)}
* and {@link GL4#glNamedBufferStorage(int, long, Buffer, int)},
* i.e. implies destruction of the buffer.
*
* @param bufferName
* @param size
* @param mutableUsage
* @throws GLException if size is less-than zero
* @throws GLException if a native GL-Error occurs
*/
public synchronized final void createBufferStorage(final GL caller,
final int bufferName, final long size, final Buffer data, final int mutableUsage, final int immutableFlags,
final CreateStorageDispatch dispatch) throws GLException {
final int glerrPre = caller.glGetError(); // clear
if (DEBUG && GL.GL_NO_ERROR != glerrPre) {
System.err.printf("%s.%s glerr-pre 0x%X%n", msgClazzName, msgCreateNamed, glerrPre);
}
if ( 0 > size ) { // glBufferData, glNamedBufferData
throw new GLException(String.format("%s: Invalid size %d for buffer %d", GL_INVALID_VALUE, size, bufferName));
}
final boolean mutableBuffer = 0 != mutableUsage;
if( !mutableBuffer ) {
throw new InternalError("Immutable glNamedBufferStorage not supported yet");
}
dispatch.create(bufferName, size, data, mutableUsage);
final int glerrPost = caller.glGetError(); // be safe, catch failure!
if(GL.GL_NO_ERROR != glerrPost) {
throw new GLException(String.format("GL-Error 0x%X while creating %s storage for buffer %d of size %d with data %s",
glerrPost, "mutable", bufferName, size, data));
}
final GLBufferStorageImpl objOld = (GLBufferStorageImpl) bufferName2StorageMap.get(bufferName);
if( null != objOld ) {
objOld.reset(size, mutableUsage, immutableFlags);
if (DEBUG) {
System.err.printf("%s.%s direct: reset %d: %s%n", msgClazzName, msgCreateNamed, bufferName, objOld);
}
} else {
final GLBufferStorageImpl objNew = new GLBufferStorageImpl(bufferName, size, mutableUsage, immutableFlags);
bufferName2StorageMap.put(bufferName, objNew);
if (DEBUG) {
System.err.printf("%s.%s direct: new %d: %s%n", msgClazzName, msgCreateNamed, bufferName, objNew);
}
}
}
/**
* Must be called when deleting GL buffer objects vis glDeleteBuffers
.
* @param count
* @param bufferNames
* @param offset
*/
public synchronized final void notifyBuffersDeleted(final int count, final int[] bufferNames, final int offset) {
for(int i=0; i* A zero store size will avoid a native call and returns the unmapped {@link GLBufferStorage}. *
** A null native mapping result indicating an error will * not cause a GLException but returns the unmapped {@link GLBufferStorage}. * This allows the user to handle this case. *
* @throws GLException if buffer is not tracked * @throws GLException if buffer is already mapped * @throws GLException if buffer has invalid store size, i.e. less-than zero * @throws GLException if buffer mapping range does not fit, incl. optional offset */ private synchronized final GLBufferStorage mapBufferImpl(final int target, final int bufferName, final boolean isNamedBuffer, final boolean useRange, long offset, long length, final int access, final MapBufferDispatch dispatch) throws GLException { final GLBufferStorageImpl store = (GLBufferStorageImpl)bufferName2StorageMap.get(bufferName); if ( null == store ) { throw new GLException("Buffer with name "+bufferName+" not tracked"); } if( null != store.getMappedBuffer() ) { throw new GLException(String.format("%s.%s: %s Buffer storage of target 0x%X -> %d: %s is already mapped", msgClazzName, msgMapBuffer, GL_INVALID_OPERATION, target, bufferName, store)); } final long storeSize = store.getSize(); if ( 0 > storeSize ) { throw new GLException(String.format("%s.%s: %s Buffer storage of target 0x%X -> %d: %s is of less-than zero", msgClazzName, msgMapBuffer, GL_INVALID_OPERATION, target, bufferName, store)); } if( !useRange ) { length = storeSize; offset = 0; } if( length + offset > storeSize ) { throw new GLException(String.format("%s.%s: %s Out of range: offset %d, length %d, buffer storage of target 0x%X -> %d: %s", msgClazzName, msgMapBuffer, GL_INVALID_VALUE, offset, length, target, bufferName, store)); } if( 0 >= length || 0 > offset ) { throw new GLException(String.format("%s.%s: %s Invalid values: offset %d, length %d, buffer storage of target 0x%X -> %d: %s", msgClazzName, msgMapBuffer, GL_INVALID_VALUE, offset, length, target, bufferName, store)); } if( 0 == storeSize ) { return store; } final long addr; if( isNamedBuffer ) { if( useRange ) { addr = ((MapBufferRangeDispatch)dispatch).mapBuffer(bufferName, offset, length, access); } else { addr = ((MapBufferAllDispatch)dispatch).mapBuffer(bufferName, access); } } else { if( useRange ) { addr = ((MapBufferRangeDispatch)dispatch).mapBuffer(target, offset, length, access); } else { addr = ((MapBufferAllDispatch)dispatch).mapBuffer(target, access); } } // GL's map-buffer implementation always returns NULL on error, // user shall validate the result and the corresponding getGLError() value! if ( 0 == addr ) { if( DEBUG ) { System.err.printf("%s.%s: %s MapBuffer null result for target 0x%X -> %d: %s, off %d, len %d, acc 0x%X%n", msgClazzName, msgMapBuffer, warning, target, bufferName, store, offset, length, access); ExceptionUtils.dumpStack(System.err); } // User shall handle the glError ! } else { final ByteBuffer buffer = dispatch.allocNioByteBuffer(addr, length); Buffers.nativeOrder(buffer); if( DEBUG ) { System.err.printf("%s.%s: Target 0x%X -> %d: %s, off %d, len %d, acc 0x%X%n", msgClazzName, msgClazzName, target, bufferName, store.toString(false), offset, length, access); } store.setMappedBuffer(buffer); } return store; } public static interface UnmapBufferDispatch { boolean unmap(final int targetOrBufferName); } /** * Must be called when unmapping GL buffer objects via {@link GL#glUnmapBuffer(int)}. ** Only clear mapped buffer reference of {@link GLBufferStorage} * if native unmapping was successful. *
*/ public synchronized final boolean unmapBuffer(final GLBufferStateTracker bufferStateTracker, final GL caller, final int target, final UnmapBufferDispatch dispatch) { final int bufferName = bufferStateTracker.getBoundBufferObject(target, caller); final GLBufferStorageImpl store; if( 0 == bufferName ) { if (DEBUG) { System.err.printf("%s: %s.%s: Buffer for target 0x%X not bound%n", warning, msgClazzName, msgUnmapped, target); ExceptionUtils.dumpStack(System.err); } store = null; } else { store = (GLBufferStorageImpl) bufferName2StorageMap.get(bufferName); if( DEBUG && null == store ) { System.err.printf("%s: %s.%s: Buffer %d not tracked%n", warning, msgClazzName, msgUnmapped, bufferName); ExceptionUtils.dumpStack(System.err); } } final boolean res = dispatch.unmap(target); if( res && null != store ) { store.setMappedBuffer(null); } if( DEBUG ) { System.err.printf("%s.%s %s target: 0x%X -> %d: %s%n", msgClazzName, msgUnmapped, res ? "OK" : "Failed", target, bufferName, store.toString(false)); if(!res) { ExceptionUtils.dumpStack(System.err); } } return res; } /** * Must be called when unmapping GL buffer objects via {@link GL2#glUnmapNamedBuffer(int)}. ** Only clear mapped buffer reference of {@link GLBufferStorage} * if native unmapping was successful. *
*/ public synchronized final boolean unmapBuffer(final int bufferName, final UnmapBufferDispatch dispatch) { final GLBufferStorageImpl store = (GLBufferStorageImpl) bufferName2StorageMap.get(bufferName); if (DEBUG && null == store ) { System.err.printf("%s: %s.%s: Buffer %d not tracked%n", warning, msgClazzName, msgUnmapped, bufferName); ExceptionUtils.dumpStack(System.err); } final boolean res = dispatch.unmap(bufferName); if( res && null != store ) { store.setMappedBuffer(null); } if (DEBUG) { System.err.printf("%s.%s %s %d: %s%n", msgClazzName, msgUnmapped, res ? "OK" : "Failed", bufferName, store.toString(false)); if(!res) { ExceptionUtils.dumpStack(System.err); } } return res; } public synchronized final GLBufferStorage getBufferStorage(final int bufferName) { return (GLBufferStorageImpl)bufferName2StorageMap.get(bufferName); } /** * Clear all tracked buffer object knowledge. ** Shall only be called at GLContext destruction iff * there are no other shared GLContext instances left. *
*/ public synchronized final void clear() { if (DEBUG) { System.err.printf("%s.clear() - Thread %s%n", msgClazzName, Thread.currentThread().getName()); // ExceptionUtils.dumpStackTrace(System.err); } bufferName2StorageMap.clear(); } private static final String warning = "WARNING"; private static final String msgClazzName = "GLBufferObjectTracker"; private static final String msgUnmapped = "notifyBufferUnmapped()"; private static final String msgCreateBound = "createBoundBufferStorage()"; private static final String msgCreateNamed = "createNamedBufferStorage()"; private static final String msgMapBuffer = "mapBuffer()"; }