minecraft-src/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java
2025-09-18 12:27:44 +00:00

502 lines
18 KiB
Java

package net.minecraft.client.renderer.chunk;
import com.google.common.collect.Queues;
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
import com.mojang.blaze3d.vertex.MeshData;
import com.mojang.blaze3d.vertex.VertexSorting;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.CrashReport;
import net.minecraft.TracingExecutor;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.renderer.SectionBufferBuilderPack;
import net.minecraft.client.renderer.SectionBufferBuilderPool;
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.SectionPos;
import net.minecraft.util.VisibleForDebug;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.Zone;
import net.minecraft.util.thread.ConsecutiveExecutor;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
@Environment(EnvType.CLIENT)
public class SectionRenderDispatcher {
private final CompileTaskDynamicQueue compileQueue = new CompileTaskDynamicQueue();
private final Queue<Runnable> toUpload = Queues.<Runnable>newConcurrentLinkedQueue();
final Executor mainThreadUploadExecutor = this.toUpload::add;
final Queue<SectionMesh> toClose = Queues.<SectionMesh>newConcurrentLinkedQueue();
final SectionBufferBuilderPack fixedBuffers;
private final SectionBufferBuilderPool bufferPool;
volatile boolean closed;
private final ConsecutiveExecutor consecutiveExecutor;
private final TracingExecutor executor;
ClientLevel level;
final LevelRenderer renderer;
Vec3 cameraPosition = Vec3.ZERO;
final SectionCompiler sectionCompiler;
public SectionRenderDispatcher(
ClientLevel level,
LevelRenderer renderer,
TracingExecutor executor,
RenderBuffers buffer,
BlockRenderDispatcher blockRenderer,
BlockEntityRenderDispatcher blockEntityRenderer
) {
this.level = level;
this.renderer = renderer;
this.fixedBuffers = buffer.fixedBufferPack();
this.bufferPool = buffer.sectionBufferPool();
this.executor = executor;
this.consecutiveExecutor = new ConsecutiveExecutor(executor, "Section Renderer");
this.consecutiveExecutor.schedule(this::runTask);
this.sectionCompiler = new SectionCompiler(blockRenderer, blockEntityRenderer);
}
public void setLevel(ClientLevel level) {
this.level = level;
}
private void runTask() {
if (!this.closed && !this.bufferPool.isEmpty()) {
SectionRenderDispatcher.RenderSection.CompileTask compileTask = this.compileQueue.poll(this.cameraPosition);
if (compileTask != null) {
SectionBufferBuilderPack sectionBufferBuilderPack = (SectionBufferBuilderPack)Objects.requireNonNull(this.bufferPool.acquire());
CompletableFuture.supplyAsync(() -> compileTask.doTask(sectionBufferBuilderPack), this.executor.forName(compileTask.name()))
.thenCompose(completableFuture -> completableFuture)
.whenComplete((sectionTaskResult, throwable) -> {
if (throwable != null) {
Minecraft.getInstance().delayCrash(CrashReport.forThrowable(throwable, "Batching sections"));
} else {
compileTask.isCompleted.set(true);
this.consecutiveExecutor.schedule(() -> {
if (sectionTaskResult == SectionRenderDispatcher.SectionTaskResult.SUCCESSFUL) {
sectionBufferBuilderPack.clearAll();
} else {
sectionBufferBuilderPack.discardAll();
}
this.bufferPool.release(sectionBufferBuilderPack);
this.runTask();
});
}
});
}
}
}
public void setCameraPosition(Vec3 cameraPosition) {
this.cameraPosition = cameraPosition;
}
public void uploadAllPendingUploads() {
Runnable runnable;
while ((runnable = (Runnable)this.toUpload.poll()) != null) {
runnable.run();
}
SectionMesh sectionMesh;
while ((sectionMesh = (SectionMesh)this.toClose.poll()) != null) {
sectionMesh.close();
}
}
public void rebuildSectionSync(SectionRenderDispatcher.RenderSection section, RenderRegionCache regionCache) {
section.compileSync(regionCache);
}
public void schedule(SectionRenderDispatcher.RenderSection.CompileTask task) {
if (!this.closed) {
this.consecutiveExecutor.schedule(() -> {
if (!this.closed) {
this.compileQueue.add(task);
this.runTask();
}
});
}
}
public void clearCompileQueue() {
this.compileQueue.clear();
}
public boolean isQueueEmpty() {
return this.compileQueue.size() == 0 && this.toUpload.isEmpty();
}
public void dispose() {
this.closed = true;
this.clearCompileQueue();
this.uploadAllPendingUploads();
}
@VisibleForDebug
public String getStats() {
return String.format(Locale.ROOT, "pC: %03d, pU: %02d, aB: %02d", this.compileQueue.size(), this.toUpload.size(), this.bufferPool.getFreeBufferCount());
}
@VisibleForDebug
public int getCompileQueueSize() {
return this.compileQueue.size();
}
@VisibleForDebug
public int getToUpload() {
return this.toUpload.size();
}
@VisibleForDebug
public int getFreeBufferCount() {
return this.bufferPool.getFreeBufferCount();
}
@Environment(EnvType.CLIENT)
public class RenderSection {
public static final int SIZE = 16;
public final int index;
public final AtomicReference<SectionMesh> sectionMesh = new AtomicReference(CompiledSectionMesh.UNCOMPILED);
@Nullable
private SectionRenderDispatcher.RenderSection.RebuildTask lastRebuildTask;
@Nullable
private SectionRenderDispatcher.RenderSection.ResortTransparencyTask lastResortTransparencyTask;
private AABB bb;
private boolean dirty = true;
volatile long sectionNode = SectionPos.asLong(-1, -1, -1);
final BlockPos.MutableBlockPos renderOrigin = new BlockPos.MutableBlockPos(-1, -1, -1);
private boolean playerChanged;
public RenderSection(final int index, final long sectionNode) {
this.index = index;
this.setSectionNode(sectionNode);
}
private boolean doesChunkExistAt(long pos) {
ChunkAccess chunkAccess = SectionRenderDispatcher.this.level.getChunk(SectionPos.x(pos), SectionPos.z(pos), ChunkStatus.FULL, false);
return chunkAccess != null && SectionRenderDispatcher.this.level.getLightEngine().lightOnInColumn(SectionPos.getZeroNode(pos));
}
public boolean hasAllNeighbors() {
return this.doesChunkExistAt(SectionPos.offset(this.sectionNode, Direction.WEST))
&& this.doesChunkExistAt(SectionPos.offset(this.sectionNode, Direction.NORTH))
&& this.doesChunkExistAt(SectionPos.offset(this.sectionNode, Direction.EAST))
&& this.doesChunkExistAt(SectionPos.offset(this.sectionNode, Direction.SOUTH))
&& this.doesChunkExistAt(SectionPos.offset(this.sectionNode, -1, 0, -1))
&& this.doesChunkExistAt(SectionPos.offset(this.sectionNode, -1, 0, 1))
&& this.doesChunkExistAt(SectionPos.offset(this.sectionNode, 1, 0, -1))
&& this.doesChunkExistAt(SectionPos.offset(this.sectionNode, 1, 0, 1));
}
public AABB getBoundingBox() {
return this.bb;
}
public CompletableFuture<Void> upload(Map<ChunkSectionLayer, MeshData> renderedLayers, CompiledSectionMesh mesh) {
if (SectionRenderDispatcher.this.closed) {
renderedLayers.values().forEach(MeshData::close);
return CompletableFuture.completedFuture(null);
} else {
return CompletableFuture.runAsync(() -> renderedLayers.forEach((chunkSectionLayer, meshData) -> {
try (Zone zone = Profiler.get().zone("Upload Section Layer")) {
mesh.uploadMeshLayer(chunkSectionLayer, meshData, this.sectionNode);
meshData.close();
}
}), SectionRenderDispatcher.this.mainThreadUploadExecutor);
}
}
public CompletableFuture<Void> uploadSectionIndexBuffer(CompiledSectionMesh mesh, ByteBufferBuilder.Result result, ChunkSectionLayer layer) {
if (SectionRenderDispatcher.this.closed) {
result.close();
return CompletableFuture.completedFuture(null);
} else {
return CompletableFuture.runAsync(() -> {
try (Zone zone = Profiler.get().zone("Upload Section Indices")) {
mesh.uploadLayerIndexBuffer(layer, result, this.sectionNode);
result.close();
}
}, SectionRenderDispatcher.this.mainThreadUploadExecutor);
}
}
public void setSectionNode(long sectionNode) {
this.reset();
this.sectionNode = sectionNode;
int i = SectionPos.sectionToBlockCoord(SectionPos.x(sectionNode));
int j = SectionPos.sectionToBlockCoord(SectionPos.y(sectionNode));
int k = SectionPos.sectionToBlockCoord(SectionPos.z(sectionNode));
this.renderOrigin.set(i, j, k);
this.bb = new AABB(i, j, k, i + 16, j + 16, k + 16);
}
public SectionMesh getSectionMesh() {
return (SectionMesh)this.sectionMesh.get();
}
public void reset() {
this.cancelTasks();
((SectionMesh)this.sectionMesh.getAndSet(CompiledSectionMesh.UNCOMPILED)).close();
this.dirty = true;
}
public BlockPos getRenderOrigin() {
return this.renderOrigin;
}
public long getSectionNode() {
return this.sectionNode;
}
public void setDirty(boolean playerChanged) {
boolean bl = this.dirty;
this.dirty = true;
this.playerChanged = playerChanged | (bl && this.playerChanged);
}
public void setNotDirty() {
this.dirty = false;
this.playerChanged = false;
}
public boolean isDirty() {
return this.dirty;
}
public boolean isDirtyFromPlayer() {
return this.dirty && this.playerChanged;
}
public long getNeighborSectionNode(Direction direction) {
return SectionPos.offset(this.sectionNode, direction);
}
public void resortTransparency(SectionRenderDispatcher dispatcher) {
if (this.getSectionMesh() instanceof CompiledSectionMesh compiledSectionMesh) {
this.lastResortTransparencyTask = new SectionRenderDispatcher.RenderSection.ResortTransparencyTask(compiledSectionMesh);
dispatcher.schedule(this.lastResortTransparencyTask);
}
}
public boolean hasTranslucentGeometry() {
return this.getSectionMesh().hasTranslucentGeometry();
}
public boolean transparencyResortingScheduled() {
return this.lastResortTransparencyTask != null && !this.lastResortTransparencyTask.isCompleted.get();
}
protected void cancelTasks() {
if (this.lastRebuildTask != null) {
this.lastRebuildTask.cancel();
this.lastRebuildTask = null;
}
if (this.lastResortTransparencyTask != null) {
this.lastResortTransparencyTask.cancel();
this.lastResortTransparencyTask = null;
}
}
public SectionRenderDispatcher.RenderSection.CompileTask createCompileTask(RenderRegionCache regionCache) {
this.cancelTasks();
RenderSectionRegion renderSectionRegion = regionCache.createRegion(SectionRenderDispatcher.this.level, this.sectionNode);
boolean bl = this.sectionMesh.get() != CompiledSectionMesh.UNCOMPILED;
this.lastRebuildTask = new SectionRenderDispatcher.RenderSection.RebuildTask(renderSectionRegion, bl);
return this.lastRebuildTask;
}
public void rebuildSectionAsync(RenderRegionCache regionCache) {
SectionRenderDispatcher.RenderSection.CompileTask compileTask = this.createCompileTask(regionCache);
SectionRenderDispatcher.this.schedule(compileTask);
}
public void compileSync(RenderRegionCache regionCache) {
SectionRenderDispatcher.RenderSection.CompileTask compileTask = this.createCompileTask(regionCache);
compileTask.doTask(SectionRenderDispatcher.this.fixedBuffers);
}
void setSectionMesh(SectionMesh sectionMesh) {
SectionMesh sectionMesh2 = (SectionMesh)this.sectionMesh.getAndSet(sectionMesh);
SectionRenderDispatcher.this.toClose.add(sectionMesh2);
SectionRenderDispatcher.this.renderer.addRecentlyCompiledSection(this);
}
VertexSorting createVertexSorting(SectionPos sectionPos) {
Vec3 vec3 = SectionRenderDispatcher.this.cameraPosition;
return VertexSorting.byDistance((float)(vec3.x - sectionPos.minBlockX()), (float)(vec3.y - sectionPos.minBlockY()), (float)(vec3.z - sectionPos.minBlockZ()));
}
@Environment(EnvType.CLIENT)
public abstract class CompileTask {
protected final AtomicBoolean isCancelled = new AtomicBoolean(false);
protected final AtomicBoolean isCompleted = new AtomicBoolean(false);
protected final boolean isRecompile;
public CompileTask(final boolean isRecompile) {
this.isRecompile = isRecompile;
}
public abstract CompletableFuture<SectionRenderDispatcher.SectionTaskResult> doTask(SectionBufferBuilderPack sectionBufferBuilderPack);
public abstract void cancel();
protected abstract String name();
public boolean isRecompile() {
return this.isRecompile;
}
public BlockPos getRenderOrigin() {
return RenderSection.this.renderOrigin;
}
}
@Environment(EnvType.CLIENT)
class RebuildTask extends SectionRenderDispatcher.RenderSection.CompileTask {
protected final RenderSectionRegion region;
public RebuildTask(final RenderSectionRegion region, final boolean isRecompile) {
super(isRecompile);
this.region = region;
}
@Override
protected String name() {
return "rend_chk_rebuild";
}
@Override
public CompletableFuture<SectionRenderDispatcher.SectionTaskResult> doTask(SectionBufferBuilderPack sectionBufferBuilderPack) {
if (this.isCancelled.get()) {
return CompletableFuture.completedFuture(SectionRenderDispatcher.SectionTaskResult.CANCELLED);
} else {
long l = RenderSection.this.sectionNode;
SectionPos sectionPos = SectionPos.of(l);
if (this.isCancelled.get()) {
return CompletableFuture.completedFuture(SectionRenderDispatcher.SectionTaskResult.CANCELLED);
} else {
SectionCompiler.Results results;
try (Zone zone = Profiler.get().zone("Compile Section")) {
results = SectionRenderDispatcher.this.sectionCompiler
.compile(sectionPos, this.region, RenderSection.this.createVertexSorting(sectionPos), sectionBufferBuilderPack);
}
TranslucencyPointOfView translucencyPointOfView = TranslucencyPointOfView.of(SectionRenderDispatcher.this.cameraPosition, l);
if (this.isCancelled.get()) {
results.release();
return CompletableFuture.completedFuture(SectionRenderDispatcher.SectionTaskResult.CANCELLED);
} else {
CompiledSectionMesh compiledSectionMesh = new CompiledSectionMesh(translucencyPointOfView, results);
CompletableFuture<Void> completableFuture = RenderSection.this.upload(results.renderedLayers, compiledSectionMesh);
return completableFuture.handle((void_, throwable) -> {
if (throwable != null && !(throwable instanceof CancellationException) && !(throwable instanceof InterruptedException)) {
Minecraft.getInstance().delayCrash(CrashReport.forThrowable(throwable, "Rendering section"));
}
if (!this.isCancelled.get() && !SectionRenderDispatcher.this.closed) {
RenderSection.this.setSectionMesh(compiledSectionMesh);
return SectionRenderDispatcher.SectionTaskResult.SUCCESSFUL;
} else {
SectionRenderDispatcher.this.toClose.add(compiledSectionMesh);
return SectionRenderDispatcher.SectionTaskResult.CANCELLED;
}
});
}
}
}
}
@Override
public void cancel() {
if (this.isCancelled.compareAndSet(false, true)) {
RenderSection.this.setDirty(false);
}
}
}
@Environment(EnvType.CLIENT)
class ResortTransparencyTask extends SectionRenderDispatcher.RenderSection.CompileTask {
private final CompiledSectionMesh compiledSectionMesh;
public ResortTransparencyTask(final CompiledSectionMesh compiledSectionMesh) {
super(true);
this.compiledSectionMesh = compiledSectionMesh;
}
@Override
protected String name() {
return "rend_chk_sort";
}
@Override
public CompletableFuture<SectionRenderDispatcher.SectionTaskResult> doTask(SectionBufferBuilderPack sectionBufferBuilderPack) {
if (this.isCancelled.get()) {
return CompletableFuture.completedFuture(SectionRenderDispatcher.SectionTaskResult.CANCELLED);
} else {
MeshData.SortState sortState = this.compiledSectionMesh.getTransparencyState();
if (sortState != null && !this.compiledSectionMesh.isEmpty(ChunkSectionLayer.TRANSLUCENT)) {
long l = RenderSection.this.sectionNode;
VertexSorting vertexSorting = RenderSection.this.createVertexSorting(SectionPos.of(l));
TranslucencyPointOfView translucencyPointOfView = TranslucencyPointOfView.of(SectionRenderDispatcher.this.cameraPosition, l);
if (!this.compiledSectionMesh.isDifferentPointOfView(translucencyPointOfView) && !translucencyPointOfView.isAxisAligned()) {
return CompletableFuture.completedFuture(SectionRenderDispatcher.SectionTaskResult.CANCELLED);
} else {
ByteBufferBuilder.Result result = sortState.buildSortedIndexBuffer(sectionBufferBuilderPack.buffer(ChunkSectionLayer.TRANSLUCENT), vertexSorting);
if (result == null) {
return CompletableFuture.completedFuture(SectionRenderDispatcher.SectionTaskResult.CANCELLED);
} else if (this.isCancelled.get()) {
result.close();
return CompletableFuture.completedFuture(SectionRenderDispatcher.SectionTaskResult.CANCELLED);
} else {
CompletableFuture<Void> completableFuture = RenderSection.this.uploadSectionIndexBuffer(this.compiledSectionMesh, result, ChunkSectionLayer.TRANSLUCENT);
return completableFuture.handle((void_, throwable) -> {
if (throwable != null && !(throwable instanceof CancellationException) && !(throwable instanceof InterruptedException)) {
Minecraft.getInstance().delayCrash(CrashReport.forThrowable(throwable, "Rendering section"));
}
if (this.isCancelled.get()) {
return SectionRenderDispatcher.SectionTaskResult.CANCELLED;
} else {
this.compiledSectionMesh.setTranslucencyPointOfView(translucencyPointOfView);
return SectionRenderDispatcher.SectionTaskResult.SUCCESSFUL;
}
});
}
}
} else {
return CompletableFuture.completedFuture(SectionRenderDispatcher.SectionTaskResult.CANCELLED);
}
}
}
@Override
public void cancel() {
this.isCancelled.set(true);
}
}
}
@Environment(EnvType.CLIENT)
static enum SectionTaskResult {
SUCCESSFUL,
CANCELLED;
}
}