Fix bugs in CQCPTree

This commit is contained in:
TomTheFurry
2022-08-20 15:49:12 +08:00
parent 0dafdc527a
commit b80bfe813c
9 changed files with 104 additions and 143 deletions
@@ -6,7 +6,6 @@ import com.seibel.lod.core.a7.datatype.column.accessor.ColumnQuadView;
import com.seibel.lod.core.a7.datatype.column.accessor.IColumnDatatype;
import com.seibel.lod.core.a7.datatype.column.render.ColumnRenderBuffer;
import com.seibel.lod.core.a7.datatype.full.ChunkSizedData;
import com.seibel.lod.core.a7.datatype.full.FullDataSource;
import com.seibel.lod.core.a7.datatype.transform.FullToColumnTransformer;
import com.seibel.lod.core.a7.level.IClientLevel;
import com.seibel.lod.core.a7.pos.DhSectionPos;
@@ -19,6 +18,8 @@ import com.seibel.lod.core.a7.level.ILevel;
import com.seibel.lod.core.a7.render.LodQuadTree;
import com.seibel.lod.core.a7.render.LodRenderSection;
import com.seibel.lod.core.a7.datatype.LodRenderSource;
import com.seibel.lod.core.util.Reference;
import com.seibel.lod.core.util.LodUtil;
import org.apache.logging.log4j.Logger;
import java.io.DataInputStream;
@@ -267,7 +268,7 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
}
private CompletableFuture<ColumnRenderBuffer> inBuildRenderBuffer = null;
private ColumnRenderBuffer usedBuffer = null;
private Reference<ColumnRenderBuffer> usedBuffer = new Reference<>();
private void tryBuildBuffer(IClientLevel level, LodQuadTree quadTree) {
@@ -327,7 +328,10 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
lastNs = System.nanoTime();
//LOGGER.info("Swapping render buffer for {}", sectionPos);
RenderBuffer oldBuffer = referenceSlot.getAndSet(inBuildRenderBuffer.join());
if (oldBuffer instanceof ColumnRenderBuffer) usedBuffer = (ColumnRenderBuffer) oldBuffer;
if (oldBuffer instanceof ColumnRenderBuffer) {
ColumnRenderBuffer swapped = usedBuffer.swap((ColumnRenderBuffer) oldBuffer);
LodUtil.assertTrue(swapped == null);
}
inBuildRenderBuffer = null;
return true;
}
@@ -3,7 +3,6 @@ package com.seibel.lod.core.a7.datatype.column.render;
import com.seibel.lod.core.a7.datatype.column.ColumnRenderSource;
import com.seibel.lod.core.a7.datatype.column.accessor.ColumnArrayView;
import com.seibel.lod.core.a7.level.IClientLevel;
import com.seibel.lod.core.a7.pos.DhBlockPos2D;
import com.seibel.lod.core.a7.render.a7LodRenderer;
import com.seibel.lod.core.a7.util.UncheckedInterruptedException;
import com.seibel.lod.core.a7.render.RenderBuffer;
@@ -18,18 +17,15 @@ import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.objects.DHBlockPos;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.render.objects.GLVertexBuffer;
import com.seibel.lod.core.util.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL32;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import static com.seibel.lod.core.render.GLProxy.GL_LOGGER;
@@ -189,7 +185,7 @@ public class ColumnRenderBuffer extends RenderBuffer {
return getCurrentJobsCount() > MAX_CONCURRENT_CALL;
}
public static CompletableFuture<ColumnRenderBuffer> build(IClientLevel clientLevel, ColumnRenderBuffer usedBuffer, ColumnRenderSource data, ColumnRenderSource[] adjData) {
public static CompletableFuture<ColumnRenderBuffer> build(IClientLevel clientLevel, Reference<ColumnRenderBuffer> usedBufferSlot, ColumnRenderSource data, ColumnRenderSource[] adjData) {
if (isBusy()) return null;
//LOGGER.info("RenderRegion startBuild @ {}", data.sectionPos);
return CompletableFuture.supplyAsync(() -> {
@@ -221,10 +217,11 @@ public class ColumnRenderBuffer extends RenderBuffer {
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
EGLProxyContext oldContext = glProxy.getGlContext();
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
ColumnRenderBuffer buffer = usedBuffer!=null ? usedBuffer :
new ColumnRenderBuffer(
new DHBlockPos(data.sectionPos.getCorner().getCorner(), clientLevel.getMinY())
);
ColumnRenderBuffer buffer = usedBufferSlot.swap(null);
if (buffer == null)
buffer = new ColumnRenderBuffer(
new DHBlockPos(data.sectionPos.getCorner().getCorner(), clientLevel.getMinY())
);
try {
buffer.uploadBuffer(builder, method);
EVENT_LOGGER.trace("RenderRegion end Upload @ {}", data.sectionPos);
@@ -244,7 +241,7 @@ public class ColumnRenderBuffer extends RenderBuffer {
}, BUFFER_UPLOADER).handle((v, e) -> {
//LOGGER.info("RenderRegion endBuild @ {}", data.sectionPos);
if (e != null) {
usedBuffer.close();
if (!usedBufferSlot.isEmpty()) usedBufferSlot.swap(null).close();
return null;
} else {
return v;
@@ -16,13 +16,14 @@ import org.apache.logging.log4j.Logger;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
public class GenerationQueue implements AutoCloseable {
final ConcurrentQuadCombinableProviderTree<GenerationResult> cqcpTree = new ConcurrentQuadCombinableProviderTree<>();
IGenerator generator = null;
private final Logger logger = DhLoggerBuilder.getLogger();
private final ConcurrentHashMap<DhLodPos, CompletableFuture<GenerationResult>> taskMap = new ConcurrentHashMap<>();
private final LinkedList<CompletableFuture<GenerationResult>> inProgress = new LinkedList<>();
private final AtomicReference<ConcurrentHashMap<DhLodPos, CompletableFuture<GenerationResult>>> inProgress = new AtomicReference<>(null);
public GenerationQueue() {}
public void pollAndStartClosest(DhBlockPos2D targetPos) {
@@ -49,15 +50,17 @@ public class GenerationQueue implements AutoCloseable {
LodUtil.assertTrue(generator != null);
LodUtil.assertTrue(this.generator == null);
this.generator = generator;
inProgress.set(new ConcurrentHashMap<>(16));
}
public void removeGenerator() {
LodUtil.assertTrue(generator != null);
this.generator = null;
inProgress.forEach(f -> f.cancel(true));
inProgress.clear();
ConcurrentHashMap<DhLodPos, CompletableFuture<GenerationResult>> swapped = this.inProgress.getAndSet(null);
swapped.forEach((k,f) -> f.cancel(true));
}
private CompletableFuture<GenerationResult> createFuture(DhLodPos pos) {
logger.info("Creating gen future for {}", pos);
CompletableFuture<GenerationResult> future = new CompletableFuture<>();
CompletableFuture<GenerationResult> swapped = taskMap.put(pos, future);
LodUtil.assertTrue(swapped == null);
@@ -80,14 +83,14 @@ public class GenerationQueue implements AutoCloseable {
int perCallChunksWidth = 1 << (genGranularity - 4);
CompletableFuture<ArrayGridList<ChunkSizedData>> dataFuture = generator.generate(chunkPosMin, genGranularity);
inProgress.add(
final ConcurrentHashMap<DhLodPos, CompletableFuture<GenerationResult>> map = this.inProgress.get();
map.put(pos, //FIXME: Slight race condition issue here with map.clear()!
dataFuture.handle((data, ex) -> {
if (ex != null) {
if (ex instanceof CompletionException) {
ex = ex.getCause();
}
if (ex instanceof InterruptedException) return null; // Ignore interrupted exceptions.
if (ex instanceof UncheckedInterruptedException) return null; // Ignore unchecked interrupted exceptions.
UncheckedInterruptedException.rethrowIfIsInterruption(ex);
logger.error("Error generating data for section {}", pos, ex);
throw new CompletionException("Generation failed", ex);
}
@@ -107,97 +110,12 @@ public class GenerationQueue implements AutoCloseable {
return result;
}).handle((r, e) -> {
if (e!=null) resultFuture.completeExceptionally(e); else resultFuture.complete(r);
map.remove(pos);
return null;
})
);
}
// private void startFuture(DhLodPos pos, CompletableFuture<GenerationResult> resultFuture) {
// byte dataDetail = generator.getDataDetail();
// byte minGenGranularity = generator.getMinGenerationGranularity();
// byte maxGenGranularity = generator.getMaxGenerationGranularity();
// if (minGenGranularity < 4 || maxGenGranularity < 4) {
// throw new IllegalStateException("Generation granularity must be at least 4!");
// }
//
// byte minUnitDetail = (byte) (dataDetail + minGenGranularity);
// byte maxUnitDetail = (byte) (dataDetail + maxGenGranularity);
//
// byte granularity;
// int count;
// DHChunkPos chunkPosMin;
// if (pos.detail < minUnitDetail) {
// granularity = minGenGranularity;
// count = 1;
// chunkPosMin = new DHChunkPos(pos.convertUpwardsTo(minUnitDetail).getCorner());
// } else if (pos.detail > maxUnitDetail) {
// granularity = maxGenGranularity;
// count = 1 << (pos.detail - maxUnitDetail);
// chunkPosMin = new DHChunkPos(pos.getCorner());
// } else {
// granularity = (byte) (pos.detail - dataDetail);
// count = 1;
// chunkPosMin = new DHChunkPos(pos.getCorner());
// }
// assert granularity >= minGenGranularity && granularity <= maxGenGranularity;
// assert count > 0;
// assert granularity >= 4; // Thanks compiler. Guess having a 'always true' warning means I did it right.
// logger.info("Generating section {} of size {} with granularity {} at {}", pos, count, granularity, chunkPosMin);
// int perCallChunksWidth = 1 << (granularity - 4);
// final byte sectionDetail = (byte) (dataDetail + FullDataSource.SECTION_SIZE_OFFSET);
//
// ArrayList<CompletableFuture<Collection<ChunkSizedData>>> futures = new ArrayList<>(count*count);
// for (int dx = 0; dx < count; dx++) {
// for (int dz = 0; dz < count; dz++) { // TODO: Unroll this loop to yield when generator is busy.
// DHChunkPos subCallChunkPosMin = new DHChunkPos(chunkPosMin.x + dx * perCallChunksWidth, chunkPosMin.z + dz * perCallChunksWidth);
// CompletableFuture<ArrayGridList<ChunkSizedData>> dataFuture = generator.generate(subCallChunkPosMin, granularity);
// futures.add(dataFuture.handle((data, ex) -> {
// if (ex != null) {
// if (ex instanceof CompletionException) {
// ex = ex.getCause();
// }
// if (ex instanceof InterruptedException) return null; // Ignore interrupted exceptions.
// if (ex instanceof UncheckedInterruptedException) return null; // Ignore unchecked interrupted exceptions.
// logger.error("Error generating data for section {}", pos, ex);
// return null;
// }
// LodUtil.assertTrue(data != null);
// if (data.gridSize < (1 << (granularity-4))) {
// logger.error(
// "Generator at {} returned {} by {} chunks but requested granularity was {}, which expect at least {} by {} chunks! ",
// pos, data.gridSize, data.gridSize, granularity, perCallChunksWidth, perCallChunksWidth);
// return null;
// }
//
// DhLodPos minSectPos = new DhLodPos((byte)(dataDetail+4), data.getFirst().x, data.getFirst().z).convertUpwardsTo(sectionDetail);
// DhLodPos maxSectPos = new DhLodPos((byte)(dataDetail+4), data.getLast().x, data.getLast().z).convertUpwardsTo(sectionDetail);
//
// int sectionCount = (maxSectPos.x - minSectPos.x) + 1;
// LodUtil.assertTrue(sectionCount > 0 && sectionCount == (maxSectPos.z - minSectPos.z) + 1);
//
// logger.info("Writing {} by {} chunks (at {}) with data detail {} to {} by {} sections (at {})",
// data.gridSize, data.gridSize, subCallChunkPosMin, dataDetail,
// sectionCount, sectionCount, minSectPos);
// return data;
// }));
// }
// }
// inProgress.add(
// CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApply((v) -> {
// GenerationResult result = new GenerationResult();
// for (CompletableFuture<Collection<ChunkSizedData>> future : futures) {
// Collection<ChunkSizedData> data = future.join();
// if (data == null) continue;
// result.dataList.addAll(data);
// }
// return result;
// }).handle((r, e) -> {
// if (e!=null) resultFuture.completeExceptionally(e); else resultFuture.complete(r);
// return null;
// })
// );
// }
public CompletableFuture<LodDataSource> generate(DhSectionPos sectionPos) {
byte maxGen = (byte) (generator.getMaxGenerationGranularity() + generator.getDataDetail());
if (sectionPos.sectionDetail > maxGen) {
@@ -221,7 +139,9 @@ public class GenerationQueue implements AutoCloseable {
if (data.getBBoxLodPos().overlaps(sectionPos.getSectionBBoxPos())) newSource.update(data);
}
} catch (Exception e) {
// continue
UncheckedInterruptedException.rethrowIfIsInterruption(e);
// else log
logger.error("Error generating data for section {}", sectionPos, e);
}
}
return newSource;
@@ -243,7 +163,6 @@ public class GenerationQueue implements AutoCloseable {
@Override
public void close() {
//TODO
}
//
@@ -102,16 +102,16 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel {
tree.close();
tree = null;
generationQueue.removeGenerator();
renderBufferHandler.close();
renderBufferHandler = null;
renderFileHandler.flushAndSave(); //Ignore the completion feature so that this action is async
renderFileHandler.close();
renderFileHandler = null;
try {
worldGenerator.close();
} catch (Exception e) {
LOGGER.error("Error closing world generator", e);
}
renderBufferHandler.close();
renderBufferHandler = null;
renderFileHandler.flushAndSave(); //Ignore the completion feature so that this action is async
renderFileHandler.close();
renderFileHandler = null;
worldGenerator = null;
}
@@ -123,6 +123,8 @@ public class DataFileHandler implements IDataSourceProvider {
DataMetaFile newMetaFile = new DataMetaFile(level, file, pos, gen);
metaFile = files.putIfAbsent(pos, newMetaFile); // This is a CAS with expected null value.
if (metaFile == null) {
//FIXME: First check for lower detail level and use them first.
dataSourceCreator.apply(pos).handle((source, ex) -> {
if (ex != null) {
LOGGER.error("Failed to create data source for {}", pos, ex);
@@ -7,6 +7,7 @@ import com.seibel.lod.core.util.LodUtil;
import org.apache.logging.log4j.Logger;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
@@ -136,7 +137,7 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
CompletableFuture<R> future = new CompletableFuture<>();
CompletableFuture<R> casValue = Atomics.compareAndExchange(node.future, null, future);
if (casValue != null) { // cas failed. Existing future. Return it.
return future;
return casValue;
}
// Next, we need to make the future completable.
@@ -153,7 +154,19 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
}
}
if (allNull) { // all children are null. We can then just run the allNullCompleter in this node.
allNullCompleter.apply(node.pos).thenAccept(future::complete);
allNullCompleter.apply(node.pos).whenComplete((r, e) -> {
// NOTE(*1): This *HAVE* to get the future via the node reference instead of directly capturing the future,
// as otherwise the node will be garbage collected before the future is completed.
// With this, we can guarantee that the node is garbage collected only when the future is (being) completed.
// (The actual order is not important however as long as the node is still alive when the generation is in progress)
CompletableFuture<R> f = node.future.get();
LodUtil.assertTrue(f != null, "Future should not be null");
if (e != null) {
f.completeExceptionally(e);
} else {
f.complete(r);
}
});
} else { // some children exist. We need to wait for some or all of them to complete.
// But before that, we need to create the children node where they are missing.
for (int i = 0; i < 4; i++) {
@@ -161,17 +174,15 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
CompletableFuture<R> newChildFuture = new CompletableFuture<>();
Node<R> newChild = new Node<>(node.pos.getChild(i), newChildFuture, node);
node.children.set(i, new WeakReference<>(newChild));
childFutures[i] = newChildFuture;
// Since the child is new, we can be sure that it doesn't have any children.
// So, we need to make the new child's future completable by running the allNullCompleter.
// (The above relies on the fact that we did a CAS on the beginning of this method,
// which means that we have unique access to the node and its links to the children, and that
// no other thread can be concurrently modifying its links)
allNullCompleter.apply(node.pos.getChild(i)).whenComplete((r, e) -> {
// NOTE(*1): This *HAVE* to get the future via the node reference instead of directly capturing the future,
// as otherwise the node will be garbage collected before the future is completed.
// With this, we can guarantee that the node is garbage collected only when the future is (being) completed.
// (The actual order is not important however as long as the node is still alive when the generation is in progress)
CompletableFuture<R> f = node.future.get();
allNullCompleter.apply(newChild.pos).whenComplete((r, e) -> {
// NOTE: Same as 'NOTE(*1)', we *HAVE* to get the future via the node reference instead of directly capturing the future.
CompletableFuture<R> f = newChild.future.get();
LodUtil.assertTrue(f != null, "Future should not be null");
if (e != null) {
f.completeExceptionally(e);
@@ -180,6 +191,7 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
}
});
}
LodUtil.assertTrue(childFutures[i] != null);
}
// Now, we can wait for all the child futures to complete, and then complete this node's future with
// the combined result of all child futures.
@@ -204,6 +216,7 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
}
public CompletableFuture<R> createOrUseExisting(DhLodPos pos, Function<DhLodPos, CompletableFuture<R>> completer) {
LOGGER.info("Creating or using existing future for {}", pos);
int cleanRng = ThreadLocalRandom.current().nextInt(0, 10);
if (cleanRng == 0) cleanIfNeeded();
// First, ensure that the root map is locked for reading. (The lock is for the structure of the map, not the values)
@@ -249,9 +262,9 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
// First iteration:
Node<R> currentNode;
DhLodPos childPos = pos.convertUpwardsTo((byte) map.topLevel);
Node<R> childNode = map.setIfNullAndGet( // rule 3: if null, create a new node.
pos.convertUpwardsTo((byte) map.topLevel),
new Node<R>(pos, null)); // No parent node as it's the root.
childPos, new Node<R>(childPos, null)); // No parent node as it's the root.
rootMapGlobalLock.readLock().unlock(); // We're done with the map, as following code no longer accesses it.
CompletableFuture<R> future = childNode.future.get();
@@ -262,10 +275,11 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
// Second and subsequent iterations:
while (currentNode.pos.detail > pos.detail) {
Node<R> newNode = new Node<R>(pos.convertUpwardsTo((byte) (currentNode.pos.detail - 1)), null, currentNode);
childPos = pos.convertUpwardsTo((byte) (currentNode.pos.detail - 1));
// Note: It is important that child link is set and created before we check the child future,
// so to avoid race conditions with checkAndMakeFuture.
childNode = currentNode.setIfNullAndGet(newNode.pos.getChildIndexOfParent(), newNode); // rule 3: if null, create a new node.
childNode = currentNode.setIfNullAndGet(childPos.getChildIndexOfParent(),
new Node<R>(childPos, null, currentNode)); // rule 3: if null, create a new node.
CompletableFuture<R> childFuture = childNode.future.get();
if (childFuture != null) { // rule 1: if future is not null, halt and return the future.
return childFuture;
@@ -26,4 +26,15 @@ public class UncheckedInterruptedException extends RuntimeException {
public static UncheckedInterruptedException convert(InterruptedException e) {
return new UncheckedInterruptedException(e);
}
public static void rethrowIfIsInterruption(Throwable t) {
if (t instanceof InterruptedException) {
throw convert((InterruptedException) t);
} else if (t instanceof UncheckedInterruptedException) {
throw (UncheckedInterruptedException) t;
}
}
public static boolean isThrowableInterruption(Throwable t) {
return t instanceof InterruptedException || t instanceof UncheckedInterruptedException;
}
}
@@ -4,6 +4,7 @@ import com.seibel.lod.core.enums.config.EGpuUploadMethod;
import com.seibel.lod.core.enums.rendering.EGLProxyContext;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.UnitBytes;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL32;
@@ -55,8 +56,8 @@ public class GLBuffer implements AutoCloseable
}
protected void create(boolean asBufferStorage) {
if (GLProxy.getInstance().getGlContext() == EGLProxyContext.NONE)
throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a GLBuffer outside a OpenGL context.");
LodUtil.assertTrue(GLProxy.getInstance().getGlContext() != EGLProxyContext.NONE,
"Thread [{}] tried to create a GLBuffer outside a OpenGL context.", Thread.currentThread());
this.id = GL32.glGenBuffers();
this.bufferStorage = asBufferStorage;
count.getAndIncrement();
@@ -65,10 +66,7 @@ public class GLBuffer implements AutoCloseable
//DEBUG USE
//private StackTraceElement[] firstCloseCallStack = null;
protected void destroy(boolean async) {
if (this.id == 0) {
//ApiShared.LOGGER.warn("Buffer double close! First close call stack: {}", Arrays.toString(firstCloseCallStack));
throw new IllegalStateException("Buffer double close!");
}
LodUtil.assertTrue(this.id != 0, "Buffer double close!");
if (async && GLProxy.getInstance().getGlContext() != EGLProxyContext.PROXY_WORKER) {
GLProxy.getInstance().recordOpenGlCall(() -> destroy((false)));
} else {
@@ -83,7 +81,7 @@ public class GLBuffer implements AutoCloseable
// Requires already binded
protected void uploadBufferStorage(ByteBuffer bb, int bufferStorageHint) {
if (!bufferStorage) throw new IllegalStateException("Buffer is not bufferStorage but its trying to use bufferStorage upload method!");
LodUtil.assertTrue(bufferStorage, "Buffer is not bufferStorage but its trying to use bufferStorage upload method!");
int bbSize = bb.limit() - bb.position();
destroy(false);
create(true);
@@ -94,7 +92,7 @@ public class GLBuffer implements AutoCloseable
// Requires already binded
protected void uploadBufferData(ByteBuffer bb, int bufferDataHint) {
if (bufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use Data upload method!");
LodUtil.assertTrue(!bufferStorage, "Buffer is bufferStorage but its trying to use bufferData upload method!");
int bbSize = bb.limit() - bb.position();
GL32.glBufferData(getBufferBindingTarget(), bb, bufferDataHint);
size = bbSize;
@@ -102,7 +100,7 @@ public class GLBuffer implements AutoCloseable
// Requires already binded
protected void uploadSubData(ByteBuffer bb, int maxExpansionSize, int bufferDataHint) {
if (bufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use SubData upload method!");
LodUtil.assertTrue(!bufferStorage, "Buffer is bufferStorage but its trying to use subData upload method!");
int bbSize = bb.limit() - bb.position();
if (size < bbSize || size > bbSize * BUFFER_SHRINK_TRIGGER) {
int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER);
@@ -115,11 +113,9 @@ public class GLBuffer implements AutoCloseable
// Requires already binded
public void uploadBuffer(ByteBuffer bb, EGpuUploadMethod uploadMethod, int maxExpansionSize, int bufferHint) {
if (uploadMethod.useEarlyMapping)
throw new IllegalArgumentException("UploadMethod signal that this should use Mapping instead of uploadBuffer!");
LodUtil.assertTrue(!uploadMethod.useEarlyMapping, "UploadMethod signal that this should use Mapping instead of uploadBuffer!");
int bbSize = bb.limit()-bb.position();
if (bbSize > maxExpansionSize)
throw new IllegalArgumentException("maxExpansionSize is "+maxExpansionSize+" but buffer size is "+bbSize+"!");
LodUtil.assertTrue(bbSize <= maxExpansionSize, "maxExpansionSize is {} but buffer size is {}!", maxExpansionSize, bbSize);
GLProxy.GL_LOGGER.debug("Uploading buffer with {}.", new UnitBytes(bbSize));
// If size is zero, just ignore it.
if (bbSize == 0) return;
@@ -131,7 +127,7 @@ public class GLBuffer implements AutoCloseable
}
switch (uploadMethod) {
case AUTO:
throw new IllegalArgumentException("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!");
LodUtil.assertNotReach("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!");
case BUFFER_STORAGE:
uploadBufferStorage(bb, bufferHint);
break;
@@ -142,14 +138,15 @@ public class GLBuffer implements AutoCloseable
uploadSubData(bb, maxExpansionSize, bufferHint);
break;
default:
throw new IllegalArgumentException("Invalid GpuUploadMethod enum");
LodUtil.assertNotReach("Unknown GpuUploadMethod!");
}
}
public ByteBuffer mapBuffer(int targetSize, EGpuUploadMethod uploadMethod, int maxExpensionSize, int bufferHint, int mapFlags) {
if (targetSize == 0) throw new IllegalArgumentException("MapBuffer targetSize is 0!");
if (!uploadMethod.useEarlyMapping) throw new IllegalStateException("Upload method must be one that use mappings in order to call mapBuffer!");
if (isMapped) throw new IllegalStateException("Map Buffer called but buffer is already mapped!");
LodUtil.assertTrue(targetSize != 0, "MapBuffer targetSize is 0");
LodUtil.assertTrue(uploadMethod.useEarlyMapping, "Upload method must be one that use early mappings in order to call mapBuffer");
LodUtil.assertTrue(!isMapped, "Buffer is already mapped");
boolean useBuffStorage = uploadMethod.useBufferStorage;
if (useBuffStorage != bufferStorage) {
destroy(false);
@@ -181,7 +178,7 @@ public class GLBuffer implements AutoCloseable
// Requires already binded
public void unmapBuffer()
{
if (!isMapped) throw new IllegalStateException("Unmap Buffer called but buffer is already not mapped!");
LodUtil.assertTrue(isMapped, "Buffer is not mapped");
bind();
GL32.glUnmapBuffer(getBufferBindingTarget());
isMapped = false;
@@ -0,0 +1,17 @@
package com.seibel.lod.core.util;
public class Reference<T> {
public T v;
public Reference() {}
public Reference(T v) {
this.v = v;
}
public T swap(T v) {
T old = this.v;
this.v = v;
return old;
}
public boolean isEmpty() {
return v == null;
}
}