Skip to content

Commit

Permalink
Use visitor pattern for visible chunk search
Browse files Browse the repository at this point in the history
This makes it possible to do multiple searches of
the graph much more easily. And it also helps to
improve performance a fair bit since we can avoid
hash map lookups.
  • Loading branch information
jellysquid3 committed Aug 5, 2023
1 parent f973cb5 commit 5cdadbd
Show file tree
Hide file tree
Showing 25 changed files with 649 additions and 515 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package me.jellysquid.mods.sodium.client.gl.shader.uniform;

import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL30C;
import org.lwjgl.system.MemoryStack;

import java.nio.FloatBuffer;

public class GlUniformMatrix4f extends GlUniform<Matrix4f> {
public class GlUniformMatrix4f extends GlUniform<Matrix4fc> {
public GlUniformMatrix4f(int index) {
super(index);
}

@Override
public void set(Matrix4f value) {
public void set(Matrix4fc value) {
try (MemoryStack stack = MemoryStack.stackPush()) {
FloatBuffer buf = stack.callocFloat(16);
value.get(buf);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ private void renderBlockEntities(MatrixStack matrices,
double z,
BlockEntityRenderDispatcher blockEntityRenderer) {
SortedRenderLists renderLists = this.renderSectionManager.getRenderLists();
Iterator<ChunkRenderList> renderListIterator = renderLists.sorted();
Iterator<ChunkRenderList> renderListIterator = renderLists.iterator();

while (renderListIterator.hasNext()) {
var renderList = renderListIterator.next();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class ChunkCameraContext {
private static final float PRECISION_MODIFIER = RenderRegion.REGION_WIDTH * 16; // 16 blocks per section

public final int blockX, blockY, blockZ;
public final int chunkX, chunkY, chunkZ;
public final float deltaX, deltaY, deltaZ;
public final double posX, posY, posZ;

Expand All @@ -16,6 +17,10 @@ public ChunkCameraContext(double x, double y, double z) {
this.blockY = (int) y;
this.blockZ = (int) z;

this.chunkX = this.blockX >> 4;
this.chunkY = this.blockY >> 4;
this.chunkZ = this.blockZ >> 4;

float deltaXFullPrecision = (float) (x - this.blockX);
float deltaYFullPrecision = (float) (y - this.blockY);
float deltaZFullPrecision = (float) (z - this.blockZ);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;

public record ChunkRenderMatrices(Matrix4f projection, Matrix4f modelView) {
public record ChunkRenderMatrices(Matrix4fc projection, Matrix4fc modelView) {
public static ChunkRenderMatrices from(MatrixStack stack) {
MatrixStack.Entry entry = stack.peek();
return new ChunkRenderMatrices(new Matrix4f(RenderSystem.getProjectionMatrix()), new Matrix4f(entry.getPositionMatrix()));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package me.jellysquid.mods.sodium.client.render.chunk;

import me.jellysquid.mods.sodium.client.gl.device.CommandList;
import me.jellysquid.mods.sodium.client.render.chunk.lists.SortedRenderLists;
import me.jellysquid.mods.sodium.client.render.chunk.ChunkCameraContext;
import me.jellysquid.mods.sodium.client.render.chunk.ChunkRenderMatrices;
import me.jellysquid.mods.sodium.client.render.chunk.lists.ChunkRenderListIterable;
import me.jellysquid.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
import me.jellysquid.mods.sodium.client.render.chunk.region.RenderRegionManager;

/**
* The chunk render backend takes care of managing the graphics resource state of chunk render containers. This includes
Expand All @@ -15,12 +16,11 @@ public interface ChunkRenderer {
*
* @param matrices The camera matrices to use for rendering
* @param commandList The command list which OpenGL commands should be serialized to
* @param regions The region storage which stores section graphics state and other information
* @param renderList An iterator over the list of chunks to be rendered
* @param renderLists The collection of render lists
* @param pass The block render pass to execute
* @param camera The camera context containing chunk offsets for the current render
*/
void render(ChunkRenderMatrices matrices, CommandList commandList, RenderRegionManager regions, SortedRenderLists renderList, TerrainRenderPass pass, ChunkCameraContext camera);
void render(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists, TerrainRenderPass pass, ChunkCameraContext camera);

/**
* Deletes this render backend and any resources attached to it.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.jellysquid.mods.sodium.client.render.chunk;

import me.jellysquid.mods.sodium.client.SodiumClientMod;
import me.jellysquid.mods.sodium.client.gl.attribute.GlVertexAttributeBinding;
import me.jellysquid.mods.sodium.client.gl.device.CommandList;
import me.jellysquid.mods.sodium.client.gl.device.DrawCommandList;
Expand All @@ -12,52 +13,58 @@
import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadFacing;
import me.jellysquid.mods.sodium.client.render.chunk.data.SectionRenderDataStorage;
import me.jellysquid.mods.sodium.client.render.chunk.data.SectionRenderDataUnsafe;
import me.jellysquid.mods.sodium.client.render.chunk.lists.ChunkRenderListIterable;
import me.jellysquid.mods.sodium.client.render.chunk.lists.ChunkRenderList;
import me.jellysquid.mods.sodium.client.render.chunk.lists.SortedRenderLists;
import me.jellysquid.mods.sodium.client.render.chunk.region.RenderRegion;
import me.jellysquid.mods.sodium.client.render.chunk.region.RenderRegionManager;
import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkShaderBindingPoints;
import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkShaderInterface;
import me.jellysquid.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
import me.jellysquid.mods.sodium.client.render.chunk.vertex.format.ChunkMeshAttribute;
import me.jellysquid.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType;
import me.jellysquid.mods.sodium.client.util.ReversibleArrayIterator;
import me.jellysquid.mods.sodium.client.util.BitwiseMath;
import org.lwjgl.system.MemoryUtil;

public class RegionChunkRenderer extends ShaderChunkRenderer {
import java.util.Iterator;

public class DefaultChunkRenderer extends ShaderChunkRenderer {
private final MultiDrawBatch batch;

private final SharedQuadIndexBuffer sharedIndexBuffer;

public RegionChunkRenderer(RenderDevice device, ChunkVertexType vertexType) {
public DefaultChunkRenderer(RenderDevice device, ChunkVertexType vertexType) {
super(device, vertexType);

this.batch = new MultiDrawBatch((ModelQuadFacing.COUNT * RenderRegion.REGION_SIZE) + 1);
this.sharedIndexBuffer = new SharedQuadIndexBuffer(device.createCommandList(), SharedQuadIndexBuffer.IndexType.INTEGER);
}

@Override
public void render(ChunkRenderMatrices matrices, CommandList commandList,
RenderRegionManager regions, SortedRenderLists renderLists, TerrainRenderPass renderPass,
public void render(ChunkRenderMatrices matrices,
CommandList commandList,
ChunkRenderListIterable renderLists,
TerrainRenderPass renderPass,
ChunkCameraContext camera) {
super.begin(renderPass);

boolean useBlockFaceCulling = SodiumClientMod.options().performance.useBlockFaceCulling;

ChunkShaderInterface shader = this.activeProgram.getInterface();
shader.setProjectionMatrix(matrices.projection());
shader.setModelViewMatrix(matrices.modelView());

ReversibleArrayIterator<ChunkRenderList> regionIterator = renderLists.sorted(renderPass.isReverseOrder());
ChunkRenderList renderList;
Iterator<ChunkRenderList> iterator = renderLists.iterator(renderPass.isReverseOrder());

while (iterator.hasNext()) {
ChunkRenderList renderList = iterator.next();

while ((renderList = regionIterator.next()) != null) {
var region = renderList.getRegion();
var storage = region.getStorage(renderPass);

if (storage == null) {
continue;
}

fillCommandBuffer(this.batch, storage, renderList, renderPass);
fillCommandBuffer(this.batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling);

if (this.batch.isEmpty()) {
continue;
Expand All @@ -74,49 +81,108 @@ public void render(ChunkRenderMatrices matrices, CommandList commandList,
super.end(renderPass);
}

private static void fillCommandBuffer(MultiDrawBatch batch, SectionRenderDataStorage storage, ChunkRenderList list, TerrainRenderPass pass) {
private static void fillCommandBuffer(MultiDrawBatch batch,
RenderRegion renderRegion,
SectionRenderDataStorage renderDataStorage,
ChunkRenderList renderList,
ChunkCameraContext camera,
TerrainRenderPass pass,
boolean useBlockFaceCulling) {
batch.clear();

var sectionIterator = list.sectionsWithGeometryIterator(pass.isReverseOrder());
var iterator = renderList.sectionsWithGeometryIterator(pass.isReverseOrder());

if (sectionIterator == null) {
if (iterator == null) {
return;
}

while (sectionIterator.hasNext()) {
var next = sectionIterator.next();
int originX = renderRegion.getChunkX();
int originY = renderRegion.getChunkY();
int originZ = renderRegion.getChunkZ();

var sectionIndex = ChunkRenderList.unpackIndex(next);
var sectionCulledFaces = ChunkRenderList.unpackFaces(next);
while (iterator.hasNext()) {
int sectionIndex = iterator.next();

var data = storage.getDataPointer(sectionIndex);
int x = originX + LocalSectionIndex.unpackX(sectionIndex);
int y = originY + LocalSectionIndex.unpackY(sectionIndex);
int z = originZ + LocalSectionIndex.unpackZ(sectionIndex);

int slices = sectionCulledFaces;
slices &= SectionRenderDataUnsafe.getSliceMask(data);
var pMeshData = renderDataStorage.getDataPointer(sectionIndex);

int slices = useBlockFaceCulling ? getVisibleFaces(camera, x, y, z) : ModelQuadFacing.ALL;
slices &= SectionRenderDataUnsafe.getSliceMask(pMeshData);

if (slices != 0) {
addDrawCommands(batch, data, slices);
addDrawCommands(batch, pMeshData, slices);
}
}
}

@SuppressWarnings("IntegerMultiplicationImplicitCastToLong")
private static void addDrawCommands(MultiDrawBatch batch, long data, int mask) {
private static void addDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) {
final var pBaseVertex = batch.pBaseVertex;
final var pElementCount = batch.pElementCount;

int size = batch.size;

for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) {
MemoryUtil.memPutInt(pBaseVertex + (size << 2), SectionRenderDataUnsafe.getVertexOffset(data, facing));
MemoryUtil.memPutInt(pElementCount + (size << 2), SectionRenderDataUnsafe.getElementCount(data, facing));
MemoryUtil.memPutInt(pBaseVertex + (size << 2), SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing));
MemoryUtil.memPutInt(pElementCount + (size << 2), SectionRenderDataUnsafe.getElementCount(pMeshData, facing));

size += (mask >> facing) & 1;
}

batch.size = size;
}

private static final int MODEL_UNASSIGNED = ModelQuadFacing.UNASSIGNED.ordinal();
private static final int MODEL_POS_X = ModelQuadFacing.POS_X.ordinal();
private static final int MODEL_NEG_X = ModelQuadFacing.NEG_X.ordinal();
private static final int MODEL_POS_Y = ModelQuadFacing.POS_Y.ordinal();
private static final int MODEL_NEG_Y = ModelQuadFacing.NEG_Y.ordinal();
private static final int MODEL_POS_Z = ModelQuadFacing.POS_Z.ordinal();
private static final int MODEL_NEG_Z = ModelQuadFacing.NEG_Z.ordinal();

private static int getVisibleFaces(ChunkCameraContext context, int x, int y, int z) {
// This is carefully written so that we can keep everything branch-less.
//
// Normally, this would be a ridiculous way to handle the problem. But the Hotspot VM's
// heuristic for generating SETcc/CMOV instructions is broken, and it will always create a
// branch even when a trivial ternary is encountered.
//
// For example, the following will never be transformed into a SETcc:
// (a > b) ? 1 : 0
//
// So we have to instead rely on sign-bit extension and masking (which generates a ton
// of unnecessary instructions) to get this to be branch-less.
//
// To do this, we can transform the previous expression into the following.
// (b - a) >> 31
//
// This works because if (a > b) then (b - a) will always create a negative number. We then shift the sign bit
// into the least significant bit's position (which also discards any bits following the sign bit) to get the
// output we are looking for.
//
// If you look at the output which LLVM produces for a series of ternaries, you will instantly become distraught,
// because it manages to a) correctly evaluate the cost of instructions, and b) go so far
// as to actually produce vector code. (https://godbolt.org/z/GaaEx39T9)

int planes = 0;

planes |= BitwiseMath.lessThan(x - 1, context.chunkX) << MODEL_POS_X;
planes |= BitwiseMath.lessThan(y - 1, context.chunkY) << MODEL_POS_Y;
planes |= BitwiseMath.lessThan(z - 1, context.chunkZ) << MODEL_POS_Z;

planes |= BitwiseMath.greaterThan(x + 1, context.chunkX) << MODEL_NEG_X;
planes |= BitwiseMath.greaterThan(y + 1, context.chunkY) << MODEL_NEG_Y;
planes |= BitwiseMath.greaterThan(z + 1, context.chunkZ) << MODEL_NEG_Z;

// the "unassigned" plane is always front-facing, since we can't check it
planes |= (1 << MODEL_UNASSIGNED);

return planes;
}

private static void setModelMatrixUniforms(ChunkShaderInterface shader, RenderRegion region, ChunkCameraContext camera) {
float x = getCameraTranslation(region.getOriginX(), camera.blockX, camera.deltaX);
float y = getCameraTranslation(region.getOriginY(), camera.blockY, camera.deltaY);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package me.jellysquid.mods.sodium.client.render.chunk;

import me.jellysquid.mods.sodium.client.render.chunk.data.BuiltSectionInfo;
import me.jellysquid.mods.sodium.client.render.chunk.graph.VisibilityEncoding;
import me.jellysquid.mods.sodium.client.render.chunk.occlusion.VisibilityEncoding;
import me.jellysquid.mods.sodium.client.render.chunk.region.RenderRegion;
import me.jellysquid.mods.sodium.client.render.viewport.Viewport;
import me.jellysquid.mods.sodium.client.util.DirectionUtil;
import me.jellysquid.mods.sodium.client.util.task.CancellationToken;
import net.minecraft.block.entity.BlockEntity;
Expand Down Expand Up @@ -306,4 +307,22 @@ public int getLastSubmittedFrame() {
public void setLastSubmittedFrame(int lastSubmittedFrame) {
this.lastSubmittedFrame = lastSubmittedFrame;
}

private static final double CHUNK_RENDER_BOUNDS_EPSILON = 1.0D / 32.0D;

public boolean isOutsideViewport(Viewport viewport) {
double x = this.getOriginX();
double y = this.getOriginY();
double z = this.getOriginZ();

double minX = x - CHUNK_RENDER_BOUNDS_EPSILON;
double minY = y - CHUNK_RENDER_BOUNDS_EPSILON;
double minZ = z - CHUNK_RENDER_BOUNDS_EPSILON;

double maxX = x + 16.0D + CHUNK_RENDER_BOUNDS_EPSILON;
double maxY = y + 16.0D + CHUNK_RENDER_BOUNDS_EPSILON;
double maxZ = z + 16.0D + CHUNK_RENDER_BOUNDS_EPSILON;

return !viewport.isBoxVisible(minX, minY, minZ, maxX, maxY, maxZ);
}
}
Loading

0 comments on commit 5cdadbd

Please sign in to comment.