Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ tab_width = 4
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_formatter_tags_enabled = true
ij_smart_tabs = false
ij_wrap_on_typing = false

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
package net.caffeinemc.mods.sodium.client.render;

import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.GpuSampler;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.SheetedDecalTextureGenerator;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.blaze3d.vertex.VertexMultiConsumer;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.caffeinemc.mods.sodium.client.SodiumClientMod;
import net.caffeinemc.mods.sodium.client.gl.device.CommandList;
Expand All @@ -21,7 +16,6 @@
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.CameraMovement;
import net.caffeinemc.mods.sodium.client.render.viewport.Viewport;
import net.caffeinemc.mods.sodium.client.services.PlatformBlockAccess;
import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation;
import net.caffeinemc.mods.sodium.client.util.FogParameters;
import net.caffeinemc.mods.sodium.client.util.NativeBuffer;
Expand All @@ -31,15 +25,14 @@
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState;
import net.minecraft.client.renderer.chunk.ChunkSectionLayerGroup;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.client.renderer.feature.ModelFeatureRenderer;
import net.minecraft.client.renderer.rendertype.RenderType;
import net.minecraft.client.renderer.state.LevelRenderState;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.server.level.BlockDestructionProgress;
Expand All @@ -50,9 +43,9 @@
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector3d;
import org.jspecify.annotations.Nullable;

import java.util.Collection;
import java.util.Collections;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class RenderSection {
private final int chunkX, chunkY, chunkZ;

// Occlusion Culling State
private long visibilityData = VisibilityEncoding.NULL;
private long[] visibilityData = null;

private int incomingDirections;
private int lastVisibleFrame = -1;
Expand Down Expand Up @@ -174,7 +174,7 @@ private boolean clearRenderState() {

this.built = false;
this.flags = RenderSectionFlags.NONE;
this.visibilityData = VisibilityEncoding.NULL;
this.visibilityData = null;
this.globalBlockEntities = null;
this.culledBlockEntities = null;
this.animatedSprites = null;
Expand Down Expand Up @@ -325,7 +325,7 @@ public int getFlags() {
/**
* Returns the occlusion culling data which determines this chunk's connectedness on the visibility graph.
*/
public long getVisibilityData() {
public long[] getVisibilityData() {
return this.visibilityData;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.BlockRenderer;
import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionInfo;
import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionMeshParts;
import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.DirectionalVisGraph;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials;
Expand Down Expand Up @@ -67,7 +68,7 @@ public ChunkBuilderMeshingTask(RenderSection render, int buildTime, Vector3dc ab
public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToken cancellationToken) {
ProfilerFiller profiler = Profiler.get();
BuiltSectionInfo.Builder renderData = new BuiltSectionInfo.Builder();
VisGraph occluder = new VisGraph();
DirectionalVisGraph occluder = new DirectionalVisGraph();

ChunkBuildBuffers buffers = buildContext.buffers;
buffers.init(renderData, this.render.getSectionIndex());
Expand Down Expand Up @@ -115,7 +116,10 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke
}

blockPos.set(x, y, z);
modelOffset.set(x & 15, y & 15, z & 15);
int localX = x & 15;
int localY = y & 15;
int localZ = z & 15;
modelOffset.set(localX, localY, localZ);

if (blockState.getRenderShape() == RenderShape.MODEL) {
BlockStateModel model = cache.getBlockModels()
Expand All @@ -142,7 +146,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke
}

if (blockState.isSolidRender()) {
occluder.setOpaque(blockPos);
occluder.setOpaque(localX, localY, localZ);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import java.util.*;
import java.util.function.IntFunction;
Expand All @@ -23,7 +23,7 @@ public class BuiltSectionInfo {
public static final BuiltSectionInfo EMPTY = createEmptyData();

public final int flags;
public final long visibilityData;
public final long[] visibilityData;

public final BlockEntity @Nullable[] globalBlockEntities;
public final BlockEntity @Nullable[] culledBlockEntities;
Expand All @@ -33,7 +33,7 @@ private BuiltSectionInfo(@NonNull Collection<TerrainRenderPass> blockRenderPasse
@NonNull Collection<BlockEntity> globalBlockEntities,
@NonNull Collection<BlockEntity> culledBlockEntities,
@NonNull Collection<TextureAtlasSprite> animatedSprites,
@NonNull VisibilitySet occlusionData) {
@NonNull VisibilitySet[] occlusionData) {
this.globalBlockEntities = toArray(globalBlockEntities, BlockEntity[]::new);
this.culledBlockEntities = toArray(culledBlockEntities, BlockEntity[]::new);
this.animatedSprites = toArray(animatedSprites, TextureAtlasSprite[]::new);
Expand All @@ -54,7 +54,10 @@ private BuiltSectionInfo(@NonNull Collection<TerrainRenderPass> blockRenderPasse

this.flags = flags;

this.visibilityData = VisibilityEncoding.encode(occlusionData);
this.visibilityData = new long[occlusionData.length];
for (int i = 0; i < occlusionData.length; i++) {
this.visibilityData[i] = VisibilityEncoding.encode(occlusionData[i]);
}
}

public static class Builder {
Expand All @@ -63,13 +66,13 @@ public static class Builder {
private final List<BlockEntity> culledBlockEntities = new ArrayList<>();
private final Set<TextureAtlasSprite> animatedSprites = new ObjectOpenHashSet<>();

private VisibilitySet occlusionData;
private VisibilitySet[] occlusionData;

public void addRenderPass(TerrainRenderPass pass) {
this.blockRenderPasses.add(pass);
}

public void setOcclusionData(VisibilitySet data) {
public void setOcclusionData(VisibilitySet[] data) {
this.occlusionData = data;
}

Expand Down Expand Up @@ -99,11 +102,11 @@ public BuiltSectionInfo build() {
}

private static BuiltSectionInfo createEmptyData() {
VisibilitySet occlusionData = new VisibilitySet();
occlusionData.add(EnumSet.allOf(Direction.class));
VisibilitySet fullyVisible = new VisibilitySet();
fullyVisible.add(EnumSet.allOf(Direction.class));

BuiltSectionInfo.Builder meshInfo = new BuiltSectionInfo.Builder();
meshInfo.setOcclusionData(occlusionData);
meshInfo.setOcclusionData(new VisibilitySet[] { fullyVisible });

return meshInfo.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package net.caffeinemc.mods.sodium.client.render.chunk.occlusion;

import net.caffeinemc.mods.sodium.client.util.collections.BitArray;
import net.minecraft.client.renderer.chunk.VisibilitySet;

public class DirectionalVisGraph {
private static final int SIZE = 16 * 16 * 16;
private static final int STACK_SIZE = 3 * 16;

private static final int DX = 1;
private static final int DY = 16 * 16;
private static final int DZ = 16;

private static final int X_MASK = 0b1111;
private static final int Y_MASK = 0b1111 << 8;
private static final int Z_MASK = 0b1111 << 4;

private static final int[] DIRECTION_SETS = new int[] {
// corresponding to GraphDirection from MSB to LSB:
// east, west, south, north, up, down
// half of the directions are omitted since their visibility is the same as the opposite direction set

//@formatter:off
0b010101, // 0: west, north, down
0b010110, // 1: west, north, up
0b011001, // 2: west, south, down
// 0b011010, // 3: west, south, up
0b100101, // 4: east, north, down
// 0b100110, // 5: east, north, up
// 0b101001, // 6: east, south, down
// 0b101010, // 7: east, south, up
//@formatter:on
};

private final BitArray blocks = new BitArray(SIZE);
private int filled = 0;

private static int getIndex(int x, int y, int z) {
return x | (z << 4) | (y << 8);
}

public void setOpaque(int x, int y, int z) {
this.blocks.set(getIndex(x, y, z));
this.filled++;
}

public VisibilitySet[] resolve() {
// if all blocks are filled, nothing is visible
if (this.filled == SIZE) {
var visibilitySet = new VisibilitySet();
visibilitySet.setAll(false);
return new VisibilitySet[] {
visibilitySet
};
}

// if fewer blocks are filled than necessary to block visibility between two faces, all faces are visible to each other
if (this.filled < 256) {
var visibilitySet = new VisibilitySet();
visibilitySet.setAll(true);
return new VisibilitySet[] {
visibilitySet
};
}

// generate visibility data for each base perspective
var results = new VisibilitySet[DIRECTION_SETS.length];
for (int i = 0; i < DIRECTION_SETS.length; i++) {
results[i] = resolveWithDirections(DIRECTION_SETS[i]);
}

return results;
}

private VisibilitySet resolveWithDirections(int directionSet) {
var visibilitySet = new VisibilitySet();

// search starting at each face opposite an allowed step direction
int originDirections = (~directionSet) & GraphDirectionSet.ALL;

var stackPos = new short[STACK_SIZE];
var stackDirs = new byte[STACK_SIZE];
for (int i = 0; i < 3; i++) {
int originDirection = Integer.numberOfTrailingZeros(originDirections);
originDirections &= ~(1 << originDirection);

int minX = 0, minY = 0, minZ = 0;
int maxX = 15, maxY = 15, maxZ = 15;
switch (originDirection) {
case GraphDirection.DOWN -> maxY = 0;
case GraphDirection.UP -> minY = 15;
case GraphDirection.NORTH -> maxZ = 0;
case GraphDirection.SOUTH -> minZ = 15;
case GraphDirection.WEST -> maxX = 0;
case GraphDirection.EAST -> minX = 15;
}

var visited = this.blocks.copy();

for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
for (int z = minZ; z <= maxZ; z++) {
int originIndex = getIndex(x, y, z);
if (!visited.getAndSet(originIndex)) {
search(visited, visibilitySet, stackPos, stackDirs, originDirection, directionSet, originIndex);
}
}
}
}
}

return visibilitySet;
}

private void search(BitArray visited, VisibilitySet visibilitySet, short[] stackPos, byte[] stackDirs, int originFace, int directionSet, int originIndex) {
int stackSize = 0;

stackPos[stackSize++] = (short) originIndex;
stackDirs[0] = (byte) directionSet;

// the faces that we cannot move towards are the ones that cannot become visible because of teh perspective of the camera. This always includes the origin face.
int connectedFaces = GraphDirectionSet.of(~directionSet);

while (stackSize > 0) {
int stackIndex = stackSize - 1;
int remainingDirs = stackDirs[stackIndex];

// backtrack when all directions have been tried
if (remainingDirs == 0) {
stackSize--;
continue;
}

int nextDir = Integer.numberOfTrailingZeros(remainingDirs);
stackDirs[stackIndex] &= (byte) ~(1 << nextDir);

int currentIndex = stackPos[stackIndex];

int neighborIndex;
boolean reachedFace;
switch (nextDir) {
case GraphDirection.DOWN -> {
neighborIndex = currentIndex - DY;
reachedFace = (neighborIndex & Y_MASK) == Y_MASK;
}
case GraphDirection.UP -> {
neighborIndex = currentIndex + DY;
reachedFace = (neighborIndex & Y_MASK) == 0;
}
case GraphDirection.NORTH -> {
neighborIndex = currentIndex - DZ;
reachedFace = (neighborIndex & Z_MASK) == Z_MASK;
}
case GraphDirection.SOUTH -> {
neighborIndex = currentIndex + DZ;
reachedFace = (neighborIndex & Z_MASK) == 0;
}
case GraphDirection.WEST -> {
neighborIndex = currentIndex - DX;
reachedFace = (neighborIndex & X_MASK) == X_MASK;
}
case GraphDirection.EAST -> {
neighborIndex = currentIndex + DX;
reachedFace = (neighborIndex & X_MASK) == 0;
}
default -> throw new IllegalStateException("Unexpected graph direction: " + nextDir);
}

// check reaching the edge of the chunk
if (reachedFace) {
// reached the edge, mark visibility between origin face and this face
connectedFaces |= GraphDirectionSet.of(nextDir);

// stop searching if all potentially reachable faces have been reached
if (connectedFaces == directionSet) {
break;
}

continue;
}

if (visited.getAndSet(neighborIndex)) {
// already visited or opaque
continue;
}

// visit the neighbor
stackPos[stackSize] = (short) neighborIndex;
stackDirs[stackSize] = (byte) directionSet;
stackSize++;
}

var originFaceEnum = GraphDirection.toEnum(originFace);
for (int direction = 0; direction < GraphDirection.COUNT; direction++) {
if (GraphDirectionSet.contains(connectedFaces, direction)) {
visibilitySet.set(originFaceEnum, GraphDirection.toEnum(direction), true);
}
}

visibilitySet.set(originFaceEnum, originFaceEnum, true);
}
}
Loading