package net.minecraft.client.renderer.chunk; import com.google.common.collect.Lists; import com.google.common.collect.Queues; import com.google.common.collect.Sets; import com.mojang.blaze3d.buffers.BufferUsage; import com.mojang.blaze3d.vertex.ByteBufferBuilder; import com.mojang.blaze3d.vertex.MeshData; import com.mojang.blaze3d.vertex.VertexBuffer; import com.mojang.blaze3d.vertex.VertexSorting; import com.mojang.blaze3d.vertex.MeshData.SortState; import it.unimi.dsi.fastutil.objects.ObjectArraySet; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.CrashReport; import net.minecraft.TracingExecutor; import net.minecraft.client.Camera; 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.RenderType; 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.client.renderer.chunk.SectionRenderDispatcher.CompiledSection.1; import net.minecraft.client.renderer.chunk.SectionRenderDispatcher.CompiledSection.2; import net.minecraft.client.renderer.chunk.SectionRenderDispatcher.RenderSection.CompileTask; import net.minecraft.client.renderer.chunk.SectionRenderDispatcher.RenderSection.RebuildTask; import net.minecraft.client.renderer.chunk.SectionRenderDispatcher.RenderSection.ResortTransparencyTask; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.SectionPos; import net.minecraft.util.Mth; import net.minecraft.util.profiling.Profiler; import net.minecraft.util.profiling.Zone; import net.minecraft.util.thread.ConsecutiveExecutor; import net.minecraft.world.level.block.entity.BlockEntity; 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 toUpload = Queues.newConcurrentLinkedQueue(); final SectionBufferBuilderPack fixedBuffers; private final SectionBufferBuilderPool bufferPool; private volatile int toBatchCount; private volatile boolean closed; private final ConsecutiveExecutor consecutiveExecutor; private final TracingExecutor executor; ClientLevel level; final LevelRenderer renderer; private Vec3 camera = Vec3.ZERO; final SectionCompiler sectionCompiler; public SectionRenderDispatcher( ClientLevel clientLevel, LevelRenderer levelRenderer, TracingExecutor tracingExecutor, RenderBuffers renderBuffers, BlockRenderDispatcher blockRenderDispatcher, BlockEntityRenderDispatcher blockEntityRenderDispatcher ) { this.level = clientLevel; this.renderer = levelRenderer; this.fixedBuffers = renderBuffers.fixedBufferPack(); this.bufferPool = renderBuffers.sectionBufferPool(); this.executor = tracingExecutor; this.consecutiveExecutor = new ConsecutiveExecutor(tracingExecutor, "Section Renderer"); this.consecutiveExecutor.schedule(this::runTask); this.sectionCompiler = new SectionCompiler(blockRenderDispatcher, blockEntityRenderDispatcher); } public void setLevel(ClientLevel level) { this.level = level; } private void runTask() { if (!this.closed && !this.bufferPool.isEmpty()) { CompileTask compileTask = this.compileQueue.poll(this.getCameraPosition()); if (compileTask != null) { SectionBufferBuilderPack sectionBufferBuilderPack = (SectionBufferBuilderPack)Objects.requireNonNull(this.bufferPool.acquire()); this.toBatchCount = this.compileQueue.size(); 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 String getStats() { return String.format(Locale.ROOT, "pC: %03d, pU: %02d, aB: %02d", this.toBatchCount, this.toUpload.size(), this.bufferPool.getFreeBufferCount()); } public int getToBatchCount() { return this.toBatchCount; } public int getToUpload() { return this.toUpload.size(); } public int getFreeBufferCount() { return this.bufferPool.getFreeBufferCount(); } public void setCamera(Vec3 camera) { this.camera = camera; } public Vec3 getCameraPosition() { return this.camera; } public void uploadAllPendingUploads() { Runnable runnable; while ((runnable = (Runnable)this.toUpload.poll()) != null) { runnable.run(); } } public void rebuildSectionSync(SectionRenderDispatcher.RenderSection section, RenderRegionCache regionCache) { section.compileSync(regionCache); } public void blockUntilClear() { this.clearBatchQueue(); } public void schedule(CompileTask task) { if (!this.closed) { this.consecutiveExecutor.schedule(() -> { if (!this.closed) { this.compileQueue.add(task); this.toBatchCount = this.compileQueue.size(); this.runTask(); } }); } } public CompletableFuture uploadSectionLayer(MeshData meshData, VertexBuffer vertexBuffer) { return this.closed ? CompletableFuture.completedFuture(null) : CompletableFuture.runAsync(() -> { if (vertexBuffer.isInvalid()) { meshData.close(); } else { try (Zone zone = Profiler.get().zone("Upload Section Layer")) { vertexBuffer.bind(); vertexBuffer.upload(meshData); VertexBuffer.unbind(); } } }, this.toUpload::add); } public CompletableFuture uploadSectionIndexBuffer(ByteBufferBuilder.Result result, VertexBuffer vertexBuffer) { return this.closed ? CompletableFuture.completedFuture(null) : CompletableFuture.runAsync(() -> { if (vertexBuffer.isInvalid()) { result.close(); } else { try (Zone zone = Profiler.get().zone("Upload Section Indices")) { vertexBuffer.bind(); vertexBuffer.uploadIndexBuffer(result); VertexBuffer.unbind(); } } }, this.toUpload::add); } private void clearBatchQueue() { this.compileQueue.clear(); this.toBatchCount = 0; } public boolean isQueueEmpty() { return this.toBatchCount == 0 && this.toUpload.isEmpty(); } public void dispose() { this.closed = true; this.clearBatchQueue(); this.uploadAllPendingUploads(); } @Environment(EnvType.CLIENT) public static class CompiledSection { public static final SectionRenderDispatcher.CompiledSection UNCOMPILED = new 1(); public static final SectionRenderDispatcher.CompiledSection EMPTY = new 2(); final Set hasBlocks = new ObjectArraySet<>(RenderType.chunkBufferLayers().size()); final List renderableBlockEntities = Lists.newArrayList(); VisibilitySet visibilitySet = new VisibilitySet(); @Nullable SortState transparencyState; public boolean hasRenderableLayers() { return !this.hasBlocks.isEmpty(); } public boolean isEmpty(RenderType renderType) { return !this.hasBlocks.contains(renderType); } public List getRenderableBlockEntities() { return this.renderableBlockEntities; } public boolean facesCanSeeEachother(Direction face1, Direction face2) { return this.visibilitySet.visibilityBetween(face1, face2); } } @Environment(EnvType.CLIENT) public class RenderSection { public static final int SIZE = 16; public final int index; public final AtomicReference compiled = new AtomicReference(SectionRenderDispatcher.CompiledSection.UNCOMPILED); public final AtomicReference pointOfView = new AtomicReference(null); @Nullable private RebuildTask lastRebuildTask; @Nullable private ResortTransparencyTask lastResortTransparencyTask; private final Set globalBlockEntities = Sets.newHashSet(); private final Map buffers = (Map)RenderType.chunkBufferLayers() .stream() .collect(Collectors.toMap(renderType -> renderType, renderType -> new VertexBuffer(BufferUsage.STATIC_WRITE))); private AABB bb; private boolean dirty = true; long sectionNode = SectionPos.asLong(-1, -1, -1); final BlockPos.MutableBlockPos origin = new BlockPos.MutableBlockPos(-1, -1, -1); private boolean playerChanged; public RenderSection(final int i, final long l) { this.index = i; this.setSectionNode(l); } private boolean doesChunkExistAt(long l) { return SectionRenderDispatcher.this.level.getChunk(SectionPos.x(l), SectionPos.z(l), ChunkStatus.FULL, false) != null; } public boolean hasAllNeighbors() { int i = 24; return !(this.getDistToPlayerSqr() > 576.0) ? true : 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)); } public AABB getBoundingBox() { return this.bb; } public VertexBuffer getBuffer(RenderType renderType) { return (VertexBuffer)this.buffers.get(renderType); } public void setSectionNode(long l) { this.reset(); this.sectionNode = l; int i = SectionPos.sectionToBlockCoord(SectionPos.x(l)); int j = SectionPos.sectionToBlockCoord(SectionPos.y(l)); int k = SectionPos.sectionToBlockCoord(SectionPos.z(l)); this.origin.set(i, j, k); this.bb = new AABB(i, j, k, i + 16, j + 16, k + 16); } protected double getDistToPlayerSqr() { Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera(); double d = this.bb.minX + 8.0 - camera.getPosition().x; double e = this.bb.minY + 8.0 - camera.getPosition().y; double f = this.bb.minZ + 8.0 - camera.getPosition().z; return d * d + e * e + f * f; } public SectionRenderDispatcher.CompiledSection getCompiled() { return (SectionRenderDispatcher.CompiledSection)this.compiled.get(); } private void reset() { this.cancelTasks(); this.compiled.set(SectionRenderDispatcher.CompiledSection.UNCOMPILED); this.pointOfView.set(null); this.dirty = true; } public void releaseBuffers() { this.reset(); this.buffers.values().forEach(VertexBuffer::close); } public BlockPos getOrigin() { return this.origin; } 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 sectionRenderDispatcher) { this.lastResortTransparencyTask = new ResortTransparencyTask(this, this.getCompiled()); sectionRenderDispatcher.schedule(this.lastResortTransparencyTask); } public boolean hasTranslucentGeometry() { return this.getCompiled().hasBlocks.contains(RenderType.translucent()); } 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 CompileTask createCompileTask(RenderRegionCache regionCache) { this.cancelTasks(); RenderChunkRegion renderChunkRegion = regionCache.createRegion(SectionRenderDispatcher.this.level, SectionPos.of(this.sectionNode)); boolean bl = this.compiled.get() != SectionRenderDispatcher.CompiledSection.UNCOMPILED; this.lastRebuildTask = new RebuildTask(this, renderChunkRegion, bl); return this.lastRebuildTask; } public void rebuildSectionAsync(SectionRenderDispatcher sectionRenderDispatcher, RenderRegionCache regionCache) { CompileTask compileTask = this.createCompileTask(regionCache); sectionRenderDispatcher.schedule(compileTask); } void updateGlobalBlockEntities(Collection blockEntities) { Set set = Sets.newHashSet(blockEntities); Set set2; synchronized (this.globalBlockEntities) { set2 = Sets.newHashSet(this.globalBlockEntities); set.removeAll(this.globalBlockEntities); set2.removeAll(blockEntities); this.globalBlockEntities.clear(); this.globalBlockEntities.addAll(blockEntities); } SectionRenderDispatcher.this.renderer.updateGlobalBlockEntities(set2, set); } public void compileSync(RenderRegionCache regionCache) { CompileTask compileTask = this.createCompileTask(regionCache); compileTask.doTask(SectionRenderDispatcher.this.fixedBuffers); } void setCompiled(SectionRenderDispatcher.CompiledSection compiled) { this.compiled.set(compiled); SectionRenderDispatcher.this.renderer.addRecentlyCompiledSection(this); } VertexSorting createVertexSorting() { Vec3 vec3 = SectionRenderDispatcher.this.getCameraPosition(); return VertexSorting.byDistance((float)(vec3.x - this.origin.getX()), (float)(vec3.y - this.origin.getY()), (float)(vec3.z - this.origin.getZ())); } } @Environment(EnvType.CLIENT) static enum SectionTaskResult { SUCCESSFUL, CANCELLED; } @Environment(EnvType.CLIENT) public static final class TranslucencyPointOfView { private int x; private int y; private int z; public static SectionRenderDispatcher.TranslucencyPointOfView of(Vec3 vec3, long l) { return new SectionRenderDispatcher.TranslucencyPointOfView().set(vec3, l); } public SectionRenderDispatcher.TranslucencyPointOfView set(Vec3 vec3, long l) { this.x = getCoordinate(vec3.x(), SectionPos.x(l)); this.y = getCoordinate(vec3.y(), SectionPos.y(l)); this.z = getCoordinate(vec3.z(), SectionPos.z(l)); return this; } private static int getCoordinate(double d, int i) { int j = SectionPos.blockToSectionCoord(d) - i; return Mth.clamp(j, -1, 1); } public boolean isAxisAligned() { return this.x == 0 || this.y == 0 || this.z == 0; } public boolean equals(Object object) { if (object == this) { return true; } else { return !(object instanceof SectionRenderDispatcher.TranslucencyPointOfView translucencyPointOfView) ? false : this.x == translucencyPointOfView.x && this.y == translucencyPointOfView.y && this.z == translucencyPointOfView.z; } } } }