Skip to content

Commit

Permalink
Fix some issues with Uint32 representation
Browse files Browse the repository at this point in the history
This increases the maximum size of vertex and index buffers
to 4 billion elements, since the Uint32 types stored in memory are
now safely represented with Int64.

For vertex buffers, this increases their maximum size to 80 GiB,
and index buffers have a maximum size of 16 GiB, whereas both
were limited to 2 GiB prior.
  • Loading branch information
jellysquid3 committed Oct 4, 2024
1 parent 267f84d commit a864415
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public class GlBufferArena {

private GlBufferSegment head;

private int capacity;
private int used;
private long capacity;
private long used;

private final int stride;

Expand All @@ -46,14 +46,14 @@ public GlBufferArena(CommandList commands, int initialCapacity, int stride, Stag
this.stagingBuffer = stagingBuffer;
}

private void resize(CommandList commandList, int newCapacity) {
private void resize(CommandList commandList, long newCapacity) {
if (this.used > newCapacity) {
throw new UnsupportedOperationException("New capacity must be larger than used size");
}

this.checkAssertions();

int tail = newCapacity - this.used;
long tail = newCapacity - this.used;

List<GlBufferSegment> usedSegments = this.getUsedSegments();
List<PendingBufferCopyCommand> pendingCopies = this.buildTransferList(usedSegments, tail);
Expand All @@ -66,31 +66,31 @@ private void resize(CommandList commandList, int newCapacity) {
if (usedSegments.isEmpty()) {
this.head.setNext(null);
} else {
this.head.setNext(usedSegments.get(0));
this.head.setNext(usedSegments.getFirst());
this.head.getNext()
.setPrev(this.head);
}

this.checkAssertions();
}

private List<PendingBufferCopyCommand> buildTransferList(List<GlBufferSegment> usedSegments, int base) {
private List<PendingBufferCopyCommand> buildTransferList(List<GlBufferSegment> usedSegments, long base) {
List<PendingBufferCopyCommand> pendingCopies = new ArrayList<>();
PendingBufferCopyCommand currentCopyCommand = null;

int writeOffset = base;
long writeOffset = base;

for (int i = 0; i < usedSegments.size(); i++) {
GlBufferSegment s = usedSegments.get(i);

if (currentCopyCommand == null || currentCopyCommand.readOffset + currentCopyCommand.length != s.getOffset()) {
if (currentCopyCommand == null || currentCopyCommand.getReadOffset() + currentCopyCommand.getLength() != s.getOffset()) {
if (currentCopyCommand != null) {
pendingCopies.add(currentCopyCommand);
}

currentCopyCommand = new PendingBufferCopyCommand(s.getOffset(), writeOffset, s.getLength());
} else {
currentCopyCommand.length += s.getLength();
currentCopyCommand.setLength(currentCopyCommand.getLength() + s.getLength());
}

s.setOffset(writeOffset);
Expand All @@ -117,17 +117,21 @@ private List<PendingBufferCopyCommand> buildTransferList(List<GlBufferSegment> u
return pendingCopies;
}

private void transferSegments(CommandList commandList, Collection<PendingBufferCopyCommand> list, int capacity) {
private void transferSegments(CommandList commandList, Collection<PendingBufferCopyCommand> list, long capacity) {
if (capacity >= (1L << 32)) {
throw new IllegalArgumentException("Maximum arena buffer size is 4 GiB");
}

GlMutableBuffer srcBufferObj = this.arenaBuffer;
GlMutableBuffer dstBufferObj = commandList.createMutableBuffer();

commandList.allocateStorage(dstBufferObj, capacity * this.stride, BUFFER_USAGE);

for (PendingBufferCopyCommand cmd : list) {
commandList.copyBufferSubData(srcBufferObj, dstBufferObj,
cmd.readOffset * this.stride,
cmd.writeOffset * this.stride,
cmd.length * this.stride);
cmd.getReadOffset() * this.stride,
cmd.getWriteOffset() * this.stride,
cmd.getLength() * this.stride);
}

commandList.deleteBuffer(srcBufferObj);
Expand All @@ -153,11 +157,11 @@ private ArrayList<GlBufferSegment> getUsedSegments() {
return used;
}

public int getDeviceUsedMemory() {
public long getDeviceUsedMemory() {
return this.used * this.stride;
}

public int getDeviceAllocatedMemory() {
public long getDeviceAllocatedMemory() {
return this.capacity * this.stride;
}

Expand Down Expand Up @@ -313,11 +317,11 @@ private boolean tryUpload(CommandList commandList, PendingUpload upload) {
return true;
}

public void ensureCapacity(CommandList commandList, int elementCount) {
public void ensureCapacity(CommandList commandList, long elementCount) {
// Re-sizing the arena results in a compaction, so any free space in the arena will be
// made into one contiguous segment, joined with the new segment of free space we're asking for
// We calculate the number of free elements in our arena and then subtract that from the total requested
int elementsNeeded = elementCount - (this.capacity - this.used);
long elementsNeeded = elementCount - (this.capacity - this.used);

// Try to allocate some extra buffer space unless this is an unusually large allocation
this.resize(commandList, Math.max(this.capacity + this.resizeIncrement, this.capacity + elementsNeeded));
Expand All @@ -331,7 +335,7 @@ private void checkAssertions() {

private void checkAssertions0() {
GlBufferSegment seg = this.head;
int used = 0;
long used = 0;

while (seg != null) {
if (seg.getOffset() < 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,45 @@
package net.caffeinemc.mods.sodium.client.gl.arena;

import net.caffeinemc.mods.sodium.client.util.UInt32;

public class GlBufferSegment {
private final GlBufferArena arena;

private boolean free = false;

private int offset;
private int length;
private int offset; /* Uint32 */
private int length; /* Uint32 */

private GlBufferSegment next;
private GlBufferSegment prev;

public GlBufferSegment(GlBufferArena arena, int offset, int length) {
public GlBufferSegment(GlBufferArena arena, long offset, long length) {
this.arena = arena;
this.offset = offset;
this.length = length;
this.offset = UInt32.downcast(offset);
this.length = UInt32.downcast(length);
}

public void delete() {
this.arena.free(this);
/* Uint32 */
protected long getEnd() {
return this.getOffset() + this.getLength();
}

protected int getEnd() {
return this.offset + this.length;
/* Uint32 */
public long getOffset() {
return UInt32.upcast(this.offset);
}

public int getLength() {
return this.length;
/* Uint32 */
public long getLength() {
return UInt32.upcast(this.length);
}

protected void setLength(int len) {
if (len <= 0) {
throw new IllegalArgumentException("len <= 0");
}

this.length = len;
protected void setOffset(long offset /* Uint32 */) {
this.offset = UInt32.downcast(offset);
}

public int getOffset() {
return this.offset;
}

protected void setOffset(int offset) {
if (offset < 0) {
throw new IllegalArgumentException("start < 0");
}

this.offset = offset;
protected void setLength(long length /* Uint32 */) {
this.length = UInt32.downcast(length);
}

protected void setFree(boolean free) {
Expand All @@ -73,6 +66,10 @@ protected void setPrev(GlBufferSegment prev) {
this.prev = prev;
}

public void delete() {
this.arena.free(this);
}

protected void mergeInto(GlBufferSegment entry) {
this.setLength(this.getLength() + entry.getLength());
this.setNext(entry.getNext());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
package net.caffeinemc.mods.sodium.client.gl.arena;

import net.caffeinemc.mods.sodium.client.util.UInt32;

class PendingBufferCopyCommand {
public final int readOffset;
public final int writeOffset;
private final int readOffset; /* Uint32 */
private final int writeOffset; /* Uint32 */

private int length;

public int length;
PendingBufferCopyCommand(long readOffset, long writeOffset, long length) {
this.readOffset = UInt32.downcast(readOffset);
this.writeOffset = UInt32.downcast(writeOffset);
this.length = UInt32.downcast(length);
}

/* Uint32 */
public long getReadOffset() {
return UInt32.upcast(this.readOffset);
}

/* Uint32 */
public long getWriteOffset() {
return UInt32.upcast(this.writeOffset);
}

/* Uint32 */
public long getLength() {
return UInt32.upcast(this.length);
}

PendingBufferCopyCommand(int readOffset, int writeOffset, int length) {
this.readOffset = readOffset;
this.writeOffset = writeOffset;
this.length = length;
public void setLength(long length /* Uint32 */) {
this.length = UInt32.downcast(length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType;
import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform;
import net.caffeinemc.mods.sodium.client.util.BitwiseMath;
import net.caffeinemc.mods.sodium.client.util.UInt32;
import org.lwjgl.system.MemoryUtil;

import java.util.Iterator;
Expand Down Expand Up @@ -148,26 +149,11 @@ private static void fillCommandBuffer(MultiDrawBatch batch,
continue;
}

addDrawCommands(batch, pMeshData, slices);
}
}

/**
* Add the draw command into the multi draw batch of the current region for one
* section. The section's mesh data is given as a pointer into the render data
* storage's allocated memory. It goes through each direction and writes the
* offsets and lengths of the already uploaded vertex and index data. The multi
* draw batch provides pointers to arrays where each of the section's data is
* stored. The batch's size counts how many commands it contains.
*/
private static void addDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) {
int elementOffset = SectionRenderDataUnsafe.getBaseElement(pMeshData);

// If high bit is set, the indices should be sourced from the arena's index buffer
if ((elementOffset & SectionRenderDataUnsafe.BASE_ELEMENT_MSB) != 0) {
addIndexedDrawCommands(batch, pMeshData, mask);
} else {
addNonIndexedDrawCommands(batch, pMeshData, mask);
if (pass.isTranslucent()) {
addIndexedDrawCommands(batch, pMeshData, slices);
} else {
addNonIndexedDrawCommands(batch, pMeshData, slices);
}
}
}

Expand All @@ -183,8 +169,9 @@ private static void addNonIndexedDrawCommands(MultiDrawBatch batch, long pMeshDa
int size = batch.size;

for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) {
MemoryUtil.memPutInt(pBaseVertex + (size << 2), SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing));
MemoryUtil.memPutInt(pElementCount + (size << 2), SectionRenderDataUnsafe.getElementCount(pMeshData, facing));
// Uint32 -> Int32 cast is always safe and should be optimized away
MemoryUtil.memPutInt(pBaseVertex + (size << 2), (int) SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing));
MemoryUtil.memPutInt(pElementCount + (size << 2), (int) SectionRenderDataUnsafe.getElementCount(pMeshData, facing));
MemoryUtil.memPutAddress(pElementPointer + (size << 3), 0 /* using a shared index buffer */);

size += (mask >> facing) & 1;
Expand All @@ -205,16 +192,17 @@ private static void addIndexedDrawCommands(MultiDrawBatch batch, long pMeshData,

int size = batch.size;

int elementOffset = SectionRenderDataUnsafe.getBaseElement(pMeshData)
& ~SectionRenderDataUnsafe.BASE_ELEMENT_MSB;
long elementOffset = SectionRenderDataUnsafe.getBaseElement(pMeshData);

for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) {
final var elementCount = SectionRenderDataUnsafe.getElementCount(pMeshData, facing);
final long vertexOffset = SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing);
final long elementCount = SectionRenderDataUnsafe.getElementCount(pMeshData, facing);

MemoryUtil.memPutInt(pBaseVertex + (size << 2), SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing));
MemoryUtil.memPutInt(pElementCount + (size << 2), elementCount);
// Uint32 -> Int32 cast is always safe and should be optimized away
MemoryUtil.memPutInt(pBaseVertex + (size << 2), UInt32.uncheckedDowncast(vertexOffset));
MemoryUtil.memPutInt(pElementCount + (size << 2), UInt32.uncheckedDowncast(elementCount));

// * 4 to convert to bytes (the buffer contains 32-bit integers)
// * 4 to convert to bytes (the index buffer contains integers)
// the section render data storage for the indices stores the offset in indices (also called elements)
MemoryUtil.memPutAddress(pElementPointer + (size << 3), elementOffset << 2);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import net.caffeinemc.mods.sodium.client.gl.arena.GlBufferSegment;
import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing;
import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion;
import net.caffeinemc.mods.sodium.client.util.UInt32;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -58,13 +59,16 @@ public void setVertexData(int localSectionIndex,
var pMeshData = this.getDataPointer(localSectionIndex);

int sliceMask = 0;
int vertexOffset = allocation.getOffset();

long vertexOffset = allocation.getOffset();

for (int facingIndex = 0; facingIndex < ModelQuadFacing.COUNT; facingIndex++) {
int vertexCount = vertexCounts[facingIndex];
long vertexCount = vertexCounts[facingIndex];

SectionRenderDataUnsafe.setVertexOffset(pMeshData, facingIndex, vertexOffset);
SectionRenderDataUnsafe.setElementCount(pMeshData, facingIndex, (vertexCount >> 2) * 6);
SectionRenderDataUnsafe.setVertexOffset(pMeshData, facingIndex,
UInt32.downcast(vertexOffset));
SectionRenderDataUnsafe.setElementCount(pMeshData, facingIndex,
UInt32.downcast((vertexCount >> 2) * 6));

if (vertexCount > 0) {
sliceMask |= 1 << facingIndex;
Expand All @@ -91,8 +95,7 @@ public void setIndexData(int localSectionIndex, GlBufferSegment allocation) {

var pMeshData = this.getDataPointer(localSectionIndex);

SectionRenderDataUnsafe.setBaseElement(pMeshData,
allocation.getOffset() | SectionRenderDataUnsafe.BASE_ELEMENT_MSB);
SectionRenderDataUnsafe.setBaseElement(pMeshData, allocation.getOffset());
}

public void removeData(int localSectionIndex) {
Expand Down Expand Up @@ -157,7 +160,7 @@ private void updateMeshes(int sectionIndex) {
return;
}

var offset = allocation.getOffset();
long offset = allocation.getOffset();
var data = this.getDataPointer(sectionIndex);

for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) {
Expand All @@ -177,8 +180,7 @@ public void onIndexBufferResized() {
var allocation = this.elementAllocations[sectionIndex];

if (allocation != null) {
SectionRenderDataUnsafe.setBaseElement(this.getDataPointer(sectionIndex),
allocation.getOffset() | SectionRenderDataUnsafe.BASE_ELEMENT_MSB);
SectionRenderDataUnsafe.setBaseElement(this.getDataPointer(sectionIndex), allocation.getOffset());
}
}
}
Expand Down
Loading

0 comments on commit a864415

Please sign in to comment.