Skip to content

Commit

Permalink
enable separate index and vertex data handling
Browse files Browse the repository at this point in the history
in SectionRenderDataStorage. reduces amount of generated and uploaded index data when sections are rebuilt without relevant changes to translucent geometry.
  • Loading branch information
douira committed Nov 4, 2023
1 parent b292841 commit f7279a0
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke

TranslucentData translucentData = null;
if (collector != null) {
translucentData = collector.getTranslucentData(meshes.get(DefaultTerrainRenderPasses.TRANSLUCENT), cameraPos);
translucentData = collector.getTranslucentData(
render.getTranslucentData(), meshes.get(DefaultTerrainRenderPasses.TRANSLUCENT), cameraPos);
}

renderData.setOcclusionData(occluder.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
* that of the vertex data except for the vertex/index scaling of two thirds,
* only an offset to the index data within the index data buffer arena is
* stored.
*
* Index and vertex data storage can be managed separately since they may be
* updated independently of each other (in both directions).
*/
public class SectionRenderDataStorage {
private final GlBufferSegment[] allocations;
Expand Down Expand Up @@ -71,9 +74,6 @@ public void setVertexData(int localSectionIndex,
}

SectionRenderDataUnsafe.setSliceMask(pMeshData, sliceMask);

// reset index offset to make sure it never renders with wrong index data
SectionRenderDataUnsafe.setIndexOffset(pMeshData, 0);
}

public void setIndexData(int localSectionIndex, GlBufferSegment allocation) {
Expand All @@ -94,20 +94,29 @@ public void setIndexData(int localSectionIndex, GlBufferSegment allocation) {
SectionRenderDataUnsafe.setIndexOffset(pMeshData, allocation.getOffset());
}

public void removeData(int localSectionIndex) {
this.removeVertexData(localSectionIndex, false);
if (this.storesIndices) {
removeIndexData(localSectionIndex);
}
}

public void removeVertexData(int localSectionIndex) {
this.removeVertexData(localSectionIndex, true);
}

private void removeVertexData(int localSectionIndex, boolean retainIndexData) {
if (this.allocations[localSectionIndex] == null) {
return;
}

this.allocations[localSectionIndex].delete();
this.allocations[localSectionIndex] = null;

// also clear index allocation
if (this.storesIndices) {
removeIndexData(localSectionIndex);
}

SectionRenderDataUnsafe.clear(this.getDataPointer(localSectionIndex));
var pMeshData = this.getDataPointer(localSectionIndex);
var indexOffset = SectionRenderDataUnsafe.getIndexOffset(pMeshData);
SectionRenderDataUnsafe.clear(pMeshData);
SectionRenderDataUnsafe.setIndexOffset(pMeshData, indexOffset);
}

public void removeIndexData(int localSectionIndex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void prepareIntegration() {
this.facePlaneDistances[i++] = relDistance;

long distanceBits = Double.doubleToLongBits(relDistance);
this.relDistanceHash ^= distanceBits;
this.relDistanceHash ^= this.relDistanceHash * 31L + distanceBits;
}

// sort the array ascending
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,11 @@ private static int getAttemptsForTime(long ns) {
}

private void sort(Vector3fc cameraPos, boolean isAngleTrigger) {
// mark as not being reused to ensure the updated buffer is actually uploaded
this.unsetReuseUploadedData();

// uses a topo sort or a distance sort depending on what is enabled
IntBuffer indexBuffer = this.buffer.getDirectBuffer().asIntBuffer();
IntBuffer indexBuffer = this.getBuffer().getDirectBuffer().asIntBuffer();

if (this.quads.length > MAX_TOPO_SORT_QUADS) {
turnGFNITriggerOff();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,10 @@ private void initiallyEnableGFNITriggering(DynamicData data, long sectionPos) {
* determined
*/
public void integrateTranslucentData(TranslucentData oldData, TranslucentData newData, Vector3dc cameraPos) {
if (oldData == newData) {
return;
}

long sectionPos = newData.sectionPos.asLong();

incrementSortTypeCounter(newData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
* Super class for translucent data that contains an actual buffer.
*/
public abstract class PresentTranslucentData extends TranslucentData {
public NativeBuffer buffer;
private NativeBuffer buffer;
private boolean reuseUploadedData;
private int quadHash;
private int length;

PresentTranslucentData(ChunkSectionPos sectionPos, NativeBuffer buffer) {
super(sectionPos);
this.buffer = buffer;
this.length = TranslucentData.indexBytesToQuadCount(buffer.getLength());
}

public abstract VertexRange[] getVertexRanges();
Expand All @@ -26,8 +30,32 @@ public void delete() {
}
}

public int getQuadLength() {
return this.buffer.getLength() / BYTES_PER_INDEX / VERTICES_PER_QUAD;
void setQuadHash(int hash) {
this.quadHash = hash;
}

int getQuadHash() {
return this.quadHash;
}

int getLength() {
return this.length;
}

public NativeBuffer getBuffer() {
return this.buffer;
}

public boolean isReusingUploadedData() {
return this.reuseUploadedData;
}

void setReuseUploadedData() {
this.reuseUploadedData = true;
}

void unsetReuseUploadedData() {
this.reuseUploadedData = false;
}

static NativeBuffer nativeBufferForQuads(TQuad[] quads) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package me.jellysquid.mods.sodium.client.render.chunk.gfni;

import java.util.Arrays;

import org.joml.Vector3f;
import org.joml.Vector3fc;

Expand All @@ -8,6 +10,45 @@
/**
* Represents a quad for the purposes of translucency sorting. Called TQuad to
* avoid confusion with other quad classes.
*
* @implNote Autogenerated hashcode and equals methods to ensure the arrays are
* correctly compared..
*/
record TQuad(ModelQuadFacing facing, Vector3fc normal, Vector3f center, float[] extents) {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((facing == null) ? 0 : facing.hashCode());
result = prime * result + ((normal == null) ? 0 : normal.hashCode());
result = prime * result + ((center == null) ? 0 : center.hashCode());
result = prime * result + Arrays.hashCode(extents);
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TQuad other = (TQuad) obj;
if (facing != other.facing)
return false;
if (normal == null) {
if (other.normal != null)
return false;
} else if (!normal.equals(other.normal))
return false;
if (center == null) {
if (other.center != null)
return false;
} else if (!center.equals(other.center))
return false;
if (!Arrays.equals(extents, other.extents))
return false;
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public abstract class TranslucentData {
public static final int INDICES_PER_QUAD = 6;
public static final int VERTICES_PER_QUAD = 4;
public static final int BYTES_PER_INDEX = 4;
public static final int BYTES_PER_QUAD = INDICES_PER_QUAD * BYTES_PER_INDEX;

public final ChunkSectionPos sectionPos;

Expand Down Expand Up @@ -45,11 +46,15 @@ public void prepareTrigger(boolean isAngleTrigger) {

static int vertexCountToIndexBytes(int vertexCount) {
// convert vertex count to quads, and then to indices, and then to bytes
return vertexCount / VERTICES_PER_QUAD * INDICES_PER_QUAD * BYTES_PER_INDEX;
return vertexCount / VERTICES_PER_QUAD * BYTES_PER_QUAD;
}

static int quadCountToIndexBytes(int quadCount) {
return quadCount * INDICES_PER_QUAD * BYTES_PER_INDEX;
return quadCount * BYTES_PER_QUAD;
}

static int indexBytesToQuadCount(int indexBytes) {
return indexBytes / BYTES_PER_QUAD;
}

static void putMappedQuadVertexIndexes(IntBuffer intBuffer, int quadIndex, int[] indexMapping) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import me.jellysquid.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder;
import me.jellysquid.mods.sodium.client.util.NativeBuffer;
import net.minecraft.util.math.ChunkSectionPos;
import java.util.Arrays;

/**
* The translucent geometry collector collects the data from the renderers and
Expand Down Expand Up @@ -46,6 +47,9 @@ public class TranslucentGeometryCollector {

private SortType sortType;

private boolean quadHashPresent = false;
private int quadHash = 0;

public TranslucentGeometryCollector(ChunkSectionPos sectionPos) {
this.sectionPos = sectionPos;
}
Expand Down Expand Up @@ -365,12 +369,7 @@ public SortType finishRendering() {
return this.sortType;
}

public TranslucentData getTranslucentData(BuiltSectionMeshParts translucentMesh, Vector3fc cameraPos) {
// means there is no translucent geometry
if (translucentMesh == null) {
return new NoData(sectionPos);
}

private TranslucentData getTranslucentDataNew(BuiltSectionMeshParts translucentMesh, Vector3fc cameraPos) {
if (this.sortType == SortType.NONE) {
return AnyOrderData.fromMesh(translucentMesh, quads, sectionPos, null);
}
Expand Down Expand Up @@ -404,6 +403,57 @@ public TranslucentData getTranslucentData(BuiltSectionMeshParts translucentMesh,
throw new IllegalStateException("Unknown sort type: " + this.sortType);
}

private int getQuadHash(TQuad[] quads) {
if (this.quadHashPresent) {
return this.quadHash;
}

for (TQuad quad : quads) {
this.quadHash ^= this.quadHash * 31 + quad.hashCode();
}
return this.quadHash;
}

public TranslucentData getTranslucentData(
TranslucentData oldData, BuiltSectionMeshParts translucentMesh, Vector3fc cameraPos) {
// means there is no translucent geometry
if (translucentMesh == null) {
return new NoData(sectionPos);
}

// re-use the original translucent data if it's the same. This reduces the
// amount of generated and uploaded index data when sections are rebuilt without
// relevant changes to translucent geometry. Rebuilds happen when any part of
// the section changes, including the here irrelevant cases of changes to opaque
// geometry or light levels.
if (oldData != null) {
// for the NONE sort type the ranges need to be the same, the actual geometry
// doesn't matter
if (this.sortType == SortType.NONE && oldData instanceof AnyOrderData oldAnyData
&& oldAnyData.getLength() == this.quads.length
&& Arrays.equals(oldAnyData.getVertexRanges(), translucentMesh.getVertexRanges())) {
oldAnyData.setReuseUploadedData();
return oldAnyData;
}

// for the other sort types the geometry needs to be the same (checked with
// length and hash)
if (oldData instanceof PresentTranslucentData oldPresentData) {
if (oldPresentData.getLength() == this.quads.length
&& oldPresentData.getQuadHash() == getQuadHash(this.quads)) {
oldPresentData.setReuseUploadedData();
return oldPresentData;
}
}
}

var newData = getTranslucentDataNew(translucentMesh, cameraPos);
if (newData instanceof PresentTranslucentData presentData) {
presentData.setQuadHash(getQuadHash(this.quads));
}
return newData;
}

AccumulationGroup getGroupForNormal(NormalList normalList) {
int collectorKey = normalList.getCollectorKey();
if (collectorKey < 0xFF) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public void removeSection(RenderSection section) {
}

for (var storage : this.sectionRenderData.values()) {
storage.removeVertexData(sectionIndex);
storage.removeData(sectionIndex);
}

this.sections[sectionIndex] = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,30 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect
}

if (result instanceof OutputWithIndexData indexDataOutput) {
var storage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT);
if (storage != null) {
storage.removeIndexData(renderSectionIndex);
}

var indexData = indexDataOutput.getTranslucentData();
boolean retainIndexData = false;
if (indexData != null) {
// TODO: debug this, seems to sometimes happen when flying around with spectator mode
// even happens without all sections as direct triggering, maybe the data on the render section is being replaced before it gets here?
if (indexData.buffer == null) {
throw new IllegalStateException("Translucent data buffer is null");
if (indexData.isReusingUploadedData()) {
retainIndexData = true;
} else {
var buffer = indexData.getBuffer();

// TODO: sometimes the buffer is null even when reuse isn't happening. maybe the data on the
// render section is being replaced before it gets here?
if (buffer == null) {
throw new IllegalStateException("Translucent data buffer is null");
}

indexUploads.add(new PendingSectionIndexBufferUpload(result.render, indexData,
new PendingUpload(buffer)));
}
}

if (!retainIndexData) {
var storage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT);
if (storage != null) {
storage.removeIndexData(renderSectionIndex);
}
indexUploads.add(new PendingSectionIndexBufferUpload(result.render, indexData,
new PendingUpload(indexData.buffer)));
}
}
}
Expand Down

0 comments on commit f7279a0

Please sign in to comment.