Skip to content

Commit

Permalink
Handle camera movement in draw batch cache invalidation (#3028)
Browse files Browse the repository at this point in the history
Fixes draw batch cache invalidation by also taking the effect of relative
camera position on which facings are rendered into account.
  • Loading branch information
douira authored Feb 16, 2025
1 parent b097296 commit 446dddc
Showing 1 changed file with 49 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ public class ChunkRenderList {
private final RenderRegion region;

private final byte[] sectionsWithGeometry = new byte[RenderRegion.REGION_SIZE];
private final byte[] prevSectionsWithGeometry = new byte[RenderRegion.REGION_SIZE];
private final long[] sectionsWithGeometryMap = new long[RenderRegion.REGION_SIZE / Long.SIZE];
private final long[] prevSectionsWithGeometryMap = new long[RenderRegion.REGION_SIZE / Long.SIZE];
private int sectionsWithGeometryCount = 0;
private int prevSectionsWithGeometryCount = 0;
private int lastRelativeCameraSectionX;
private int lastRelativeCameraSectionY;
private int lastRelativeCameraSectionZ;

private final byte[] sectionsWithSprites = new byte[RenderRegion.REGION_SIZE];
private int sectionsWithSpritesCount = 0;
Expand All @@ -37,6 +41,7 @@ public ChunkRenderList(RenderRegion region) {

public void reset(int frame) {
this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount;
Arrays.fill(this.sectionsWithGeometryMap, 0L);

this.sectionsWithGeometryCount = 0;
this.sectionsWithSpritesCount = 0;
Expand All @@ -50,35 +55,57 @@ public void reset(int frame) {
private static final int SORTING_HISTOGRAM_SIZE = RenderRegion.REGION_WIDTH + RenderRegion.REGION_HEIGHT + RenderRegion.REGION_LENGTH - 2;

public void prepareForRender(SectionPos cameraPos, int[] sortItems) {
this.sortSections(cameraPos, sortItems);
// The relative coordinates are clamped to one section larger than the region bounds to also capture cache invalidation that happens
// when the camera moves from outside the region to inside the region (when seen on all axes independently).
// This type of cache invalidation stems from different facings of sections being rendered if the camera is aligned with them on an axis.
// For sorting only the position clamped to inside the region is used.
int relativeCameraSectionX = Mth.clamp(cameraPos.getX() - this.region.getChunkX(), -1, RenderRegion.REGION_WIDTH);
int relativeCameraSectionY = Mth.clamp(cameraPos.getY() - this.region.getChunkY(), -1, RenderRegion.REGION_HEIGHT);
int relativeCameraSectionZ = Mth.clamp(cameraPos.getZ() - this.region.getChunkZ(), -1, RenderRegion.REGION_LENGTH);

// invalidate batch cache if the render list changed
if (this.prevSectionsWithGeometryCount != this.sectionsWithGeometryCount ||
!Arrays.equals(this.sectionsWithGeometry, this.prevSectionsWithGeometry)) {
relativeCameraSectionX != this.lastRelativeCameraSectionX ||
relativeCameraSectionY != this.lastRelativeCameraSectionY ||
relativeCameraSectionZ != this.lastRelativeCameraSectionZ ||
!Arrays.equals(this.sectionsWithGeometryMap, this.prevSectionsWithGeometryMap)) {
// reset cache invalidation, the newly built batches will remain valid until the next change
this.region.clearAllCachedBatches();

// reset cache invalidation, the newly built batch will remain valid until the next change
this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount;
System.arraycopy(this.sectionsWithGeometry, 0, this.prevSectionsWithGeometry, 0, RenderRegion.REGION_SIZE);
System.arraycopy(this.sectionsWithGeometryMap, 0, this.prevSectionsWithGeometryMap, 0, this.sectionsWithGeometryMap.length);
this.lastRelativeCameraSectionX = relativeCameraSectionX;
this.lastRelativeCameraSectionY = relativeCameraSectionY;
this.lastRelativeCameraSectionZ = relativeCameraSectionZ;

this.sortSections(relativeCameraSectionX, relativeCameraSectionY, relativeCameraSectionZ, sortItems);
}
}

public void sortSections(SectionPos cameraPos, int[] sortItems) {
var cameraX = Mth.clamp(cameraPos.getX() - this.region.getChunkX(), 0, RenderRegion.REGION_WIDTH - 1);
var cameraY = Mth.clamp(cameraPos.getY() - this.region.getChunkY(), 0, RenderRegion.REGION_HEIGHT - 1);
var cameraZ = Mth.clamp(cameraPos.getZ() - this.region.getChunkZ(), 0, RenderRegion.REGION_LENGTH - 1);
public void sortSections(int relativeCameraSectionX, int relativeCameraSectionY, int relativeCameraSectionZ, int[] sortItems) {
relativeCameraSectionX = Mth.clamp(relativeCameraSectionX, 0, RenderRegion.REGION_WIDTH - 1);
relativeCameraSectionY = Mth.clamp(relativeCameraSectionY, 0, RenderRegion.REGION_HEIGHT - 1);
relativeCameraSectionZ = Mth.clamp(relativeCameraSectionZ, 0, RenderRegion.REGION_LENGTH - 1);

int[] histogram = new int[SORTING_HISTOGRAM_SIZE];

for (int i = 0; i < this.sectionsWithGeometryCount; i++) {
var index = this.sectionsWithGeometry[i] & 0xFF; // makes sure the byte -> int conversion is unsigned
var x = Math.abs(LocalSectionIndex.unpackX(index) - cameraX);
var y = Math.abs(LocalSectionIndex.unpackY(index) - cameraY);
var z = Math.abs(LocalSectionIndex.unpackZ(index) - cameraZ);

var distance = x + y + z;
histogram[distance]++;
sortItems[i] = distance << 8 | index;
this.sectionsWithGeometryCount = 0;
for (int mapIndex = 0; mapIndex < this.sectionsWithGeometryMap.length; mapIndex++) {
var map = this.sectionsWithGeometryMap[mapIndex];
var mapOffset = mapIndex << 6;

while (map != 0) {
var index = Long.numberOfTrailingZeros(map) + mapOffset;
map &= map - 1;

var x = Math.abs(LocalSectionIndex.unpackX(index) - relativeCameraSectionX);
var y = Math.abs(LocalSectionIndex.unpackY(index) - relativeCameraSectionY);
var z = Math.abs(LocalSectionIndex.unpackZ(index) - relativeCameraSectionZ);

var distance = x + y + z;
histogram[distance]++;
sortItems[this.sectionsWithGeometryCount++] = distance << 8 | index;
}
}

// prefix sum to calculate indexes
Expand All @@ -103,8 +130,10 @@ public void add(RenderSection render) {
int index = render.getSectionIndex();
int flags = render.getFlags();

this.sectionsWithGeometry[this.sectionsWithGeometryCount] = (byte) index;
this.sectionsWithGeometryCount += (flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1;
if (((flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1) == 1) {
this.sectionsWithGeometryMap[index >> 6] |= 1L << (index & 0b111111);
this.sectionsWithGeometryCount++;
}

this.sectionsWithSprites[this.sectionsWithSpritesCount] = (byte) index;
this.sectionsWithSpritesCount += (flags >>> RenderSectionFlags.HAS_ANIMATED_SPRITES) & 1;
Expand Down

0 comments on commit 446dddc

Please sign in to comment.