minecraft-src/com/mojang/blaze3d/vertex/ByteBufferBuilder.java
2025-07-04 02:00:41 +03:00

161 lines
4.1 KiB
Java

package com.mojang.blaze3d.vertex;
import com.mojang.jtracy.MemoryPool;
import com.mojang.jtracy.TracyClient;
import com.mojang.logging.LogUtils;
import java.nio.ByteBuffer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.system.MemoryUtil.MemoryAllocator;
import org.slf4j.Logger;
@Environment(EnvType.CLIENT)
public class ByteBufferBuilder implements AutoCloseable {
private static final MemoryPool MEMORY_POOL = TracyClient.createMemoryPool("ByteBufferBuilder");
private static final Logger LOGGER = LogUtils.getLogger();
private static final MemoryAllocator ALLOCATOR = MemoryUtil.getAllocator(false);
private static final int MAX_GROWTH_SIZE = 2097152;
private static final int BUFFER_FREED_GENERATION = -1;
long pointer;
private int capacity;
private int writeOffset;
private int nextResultOffset;
private int resultCount;
private int generation;
public ByteBufferBuilder(int capacity) {
this.capacity = capacity;
this.pointer = ALLOCATOR.malloc(capacity);
MEMORY_POOL.malloc(this.pointer, capacity);
if (this.pointer == 0L) {
throw new OutOfMemoryError("Failed to allocate " + capacity + " bytes");
}
}
public long reserve(int bytes) {
int i = this.writeOffset;
int j = i + bytes;
this.ensureCapacity(j);
this.writeOffset = j;
return this.pointer + i;
}
private void ensureCapacity(int size) {
if (size > this.capacity) {
int i = Math.min(this.capacity, 2097152);
int j = Math.max(this.capacity + i, size);
this.resize(j);
}
}
private void resize(int newSize) {
MEMORY_POOL.free(this.pointer);
this.pointer = ALLOCATOR.realloc(this.pointer, newSize);
MEMORY_POOL.malloc(this.pointer, newSize);
LOGGER.debug("Needed to grow BufferBuilder buffer: Old size {} bytes, new size {} bytes.", this.capacity, newSize);
if (this.pointer == 0L) {
throw new OutOfMemoryError("Failed to resize buffer from " + this.capacity + " bytes to " + newSize + " bytes");
} else {
this.capacity = newSize;
}
}
@Nullable
public ByteBufferBuilder.Result build() {
this.checkOpen();
int i = this.nextResultOffset;
int j = this.writeOffset - i;
if (j == 0) {
return null;
} else {
this.nextResultOffset = this.writeOffset;
this.resultCount++;
return new ByteBufferBuilder.Result(i, j, this.generation);
}
}
public void clear() {
if (this.resultCount > 0) {
LOGGER.warn("Clearing BufferBuilder with unused batches");
}
this.discard();
}
public void discard() {
this.checkOpen();
if (this.resultCount > 0) {
this.discardResults();
this.resultCount = 0;
}
}
boolean isValid(int generation) {
return generation == this.generation;
}
void freeResult() {
if (--this.resultCount <= 0) {
this.discardResults();
}
}
private void discardResults() {
int i = this.writeOffset - this.nextResultOffset;
if (i > 0) {
MemoryUtil.memCopy(this.pointer + this.nextResultOffset, this.pointer, i);
}
this.writeOffset = i;
this.nextResultOffset = 0;
this.generation++;
}
public void close() {
if (this.pointer != 0L) {
MEMORY_POOL.free(this.pointer);
ALLOCATOR.free(this.pointer);
this.pointer = 0L;
this.generation = -1;
}
}
private void checkOpen() {
if (this.pointer == 0L) {
throw new IllegalStateException("Buffer has been freed");
}
}
@Environment(EnvType.CLIENT)
public class Result implements AutoCloseable {
private final int offset;
private final int capacity;
private final int generation;
private boolean closed;
Result(final int offset, final int capacity, final int generation) {
this.offset = offset;
this.capacity = capacity;
this.generation = generation;
}
public ByteBuffer byteBuffer() {
if (!ByteBufferBuilder.this.isValid(this.generation)) {
throw new IllegalStateException("Buffer is no longer valid");
} else {
return MemoryUtil.memByteBuffer(ByteBufferBuilder.this.pointer + this.offset, this.capacity);
}
}
public void close() {
if (!this.closed) {
this.closed = true;
if (ByteBufferBuilder.this.isValid(this.generation)) {
ByteBufferBuilder.this.freeResult();
}
}
}
}
}