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.BufferType; import com.mojang.blaze3d.buffers.BufferUsage; import com.mojang.blaze3d.buffers.GpuBuffer; import com.mojang.blaze3d.systems.CommandEncoder; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.MeshData; import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexSorting; import com.mojang.blaze3d.vertex.ByteBufferBuilder.Result; import com.mojang.blaze3d.vertex.MeshData.SortState; import it.unimi.dsi.fastutil.objects.ObjectArraySet; import java.util.Collection; import java.util.HashMap; 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 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.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(); final Queue toUpload = Queues.newConcurrentLinkedQueue(); final SectionBufferBuilderPack fixedBuffers; private final SectionBufferBuilderPool bufferPool; private volatile int toBatchCount; 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 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()) { 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(); } }); } } 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 = new HashMap(); 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() { 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)) && 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; } @Nullable public SectionRenderDispatcher.SectionBuffers getBuffers(RenderType renderType) { return (SectionRenderDispatcher.SectionBuffers)this.buffers.get(renderType); } public CompletableFuture uploadSectionLayer(RenderType renderType, MeshData meshData) { if (SectionRenderDispatcher.this.closed) { meshData.close(); return CompletableFuture.completedFuture(null); } else { return CompletableFuture.runAsync( () -> { try (Zone zone = Profiler.get().zone("Upload Section Layer")) { CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder(); if (this.buffers.containsKey(renderType)) { SectionRenderDispatcher.SectionBuffers sectionBuffers = (SectionRenderDispatcher.SectionBuffers)this.buffers.get(renderType); if (sectionBuffers.vertexBuffer.size() < meshData.vertexBuffer().remaining()) { sectionBuffers.vertexBuffer.close(); sectionBuffers.setVertexBuffer( RenderSystem.getDevice() .createBuffer( () -> "Section vertex buffer - layer: " + renderType.getName() + "; cords: " + SectionPos.x(this.sectionNode) + ", " + SectionPos.y(this.sectionNode) + ", " + SectionPos.z(this.sectionNode), BufferType.VERTICES, BufferUsage.STATIC_WRITE, meshData.vertexBuffer() ) ); } else if (!sectionBuffers.vertexBuffer.isClosed()) { commandEncoder.writeToBuffer(sectionBuffers.vertexBuffer, meshData.vertexBuffer(), 0); } if (meshData.indexBuffer() != null) { if (sectionBuffers.indexBuffer != null && sectionBuffers.indexBuffer.size() >= meshData.indexBuffer().remaining()) { if (!sectionBuffers.indexBuffer.isClosed()) { commandEncoder.writeToBuffer(sectionBuffers.indexBuffer, meshData.indexBuffer(), 0); } } else { if (sectionBuffers.indexBuffer != null) { sectionBuffers.indexBuffer.close(); } sectionBuffers.setIndexBuffer( RenderSystem.getDevice() .createBuffer( () -> "Section index buffer - layer: " + renderType.getName() + "; cords: " + SectionPos.x(this.sectionNode) + ", " + SectionPos.y(this.sectionNode) + ", " + SectionPos.z(this.sectionNode), BufferType.INDICES, BufferUsage.STATIC_WRITE, meshData.indexBuffer() ) ); } } else if (sectionBuffers.indexBuffer != null) { sectionBuffers.indexBuffer.close(); sectionBuffers.setIndexBuffer(null); } sectionBuffers.setIndexCount(meshData.drawState().indexCount()); sectionBuffers.setIndexType(meshData.drawState().indexType()); } else { GpuBuffer gpuBuffer = RenderSystem.getDevice() .createBuffer( () -> "Section vertex buffer - layer: " + renderType.getName() + "; cords: " + SectionPos.x(this.sectionNode) + ", " + SectionPos.y(this.sectionNode) + ", " + SectionPos.z(this.sectionNode), BufferType.VERTICES, BufferUsage.STATIC_WRITE, meshData.vertexBuffer() ); GpuBuffer gpuBuffer2 = meshData.indexBuffer() != null ? RenderSystem.getDevice() .createBuffer( () -> "Section index buffer - layer: " + renderType.getName() + "; cords: " + SectionPos.x(this.sectionNode) + ", " + SectionPos.y(this.sectionNode) + ", " + SectionPos.z(this.sectionNode), BufferType.INDICES, BufferUsage.STATIC_WRITE, meshData.indexBuffer() ) : null; SectionRenderDispatcher.SectionBuffers sectionBuffers2 = new SectionRenderDispatcher.SectionBuffers( gpuBuffer, gpuBuffer2, meshData.drawState().indexCount(), meshData.drawState().indexType() ); this.buffers.put(renderType, sectionBuffers2); } meshData.close(); } }, SectionRenderDispatcher.this.toUpload::add ); } } public CompletableFuture uploadSectionIndexBuffer(Result result, RenderType renderType) { if (SectionRenderDispatcher.this.closed) { result.close(); return CompletableFuture.completedFuture(null); } else { return CompletableFuture.runAsync( () -> { try (Zone zone = Profiler.get().zone("Upload Section Indices")) { SectionRenderDispatcher.SectionBuffers sectionBuffers = this.getBuffers(renderType); if (sectionBuffers != null && !SectionRenderDispatcher.this.closed) { if (sectionBuffers.indexBuffer == null) { sectionBuffers.setIndexBuffer( RenderSystem.getDevice() .createBuffer( () -> "Section index buffer - layer: " + renderType.getName() + "; cords: " + SectionPos.x(this.sectionNode) + ", " + SectionPos.y(this.sectionNode) + ", " + SectionPos.z(this.sectionNode), BufferType.INDICES, BufferUsage.STATIC_WRITE, result.byteBuffer() ) ); } else { CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder(); if (!sectionBuffers.indexBuffer.isClosed()) { commandEncoder.writeToBuffer(sectionBuffers.indexBuffer, result.byteBuffer(), 0); } } result.close(); return; } result.close(); } }, SectionRenderDispatcher.this.toUpload::add ); } } 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); } 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(); } public void reset() { this.cancelTasks(); this.compiled.set(SectionRenderDispatcher.CompiledSection.UNCOMPILED); this.pointOfView.set(null); this.dirty = true; this.buffers.values().forEach(SectionRenderDispatcher.SectionBuffers::close); this.buffers.clear(); } 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) { this.lastResortTransparencyTask = new ResortTransparencyTask(this, this.getCompiled()); dispatcher.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(SectionPos sectionPos) { Vec3 vec3 = SectionRenderDispatcher.this.getCameraPosition(); return VertexSorting.byDistance((float)(vec3.x - sectionPos.minBlockX()), (float)(vec3.y - sectionPos.minBlockY()), (float)(vec3.z - sectionPos.minBlockZ())); } } @Environment(EnvType.CLIENT) public static final class SectionBuffers implements AutoCloseable { GpuBuffer vertexBuffer; @Nullable GpuBuffer indexBuffer; private int indexCount; private VertexFormat.IndexType indexType; public SectionBuffers(GpuBuffer vertexBuffer, @Nullable GpuBuffer indexBuffer, int indexCount, VertexFormat.IndexType indexType) { this.vertexBuffer = vertexBuffer; this.indexBuffer = indexBuffer; this.indexCount = indexCount; this.indexType = indexType; } public GpuBuffer getVertexBuffer() { return this.vertexBuffer; } @Nullable public GpuBuffer getIndexBuffer() { return this.indexBuffer; } public void setIndexBuffer(@Nullable GpuBuffer indexBuffer) { this.indexBuffer = indexBuffer; } public int getIndexCount() { return this.indexCount; } public VertexFormat.IndexType getIndexType() { return this.indexType; } public void setIndexType(VertexFormat.IndexType indexType) { this.indexType = indexType; } public void setIndexCount(int indexCount) { this.indexCount = indexCount; } public void setVertexBuffer(GpuBuffer vertexBuffer) { this.vertexBuffer = vertexBuffer; } public void close() { this.vertexBuffer.close(); if (this.indexBuffer != null) { this.indexBuffer.close(); } } } @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 cameraPosition, long sectionNode) { return new SectionRenderDispatcher.TranslucencyPointOfView().set(cameraPosition, sectionNode); } public SectionRenderDispatcher.TranslucencyPointOfView set(Vec3 cameraPosition, long sectionNode) { this.x = getCoordinate(cameraPosition.x(), SectionPos.x(sectionNode)); this.y = getCoordinate(cameraPosition.y(), SectionPos.y(sectionNode)); this.z = getCoordinate(cameraPosition.z(), SectionPos.z(sectionNode)); return this; } private static int getCoordinate(double cameraCoord, int sectionCoord) { int i = SectionPos.blockToSectionCoord(cameraCoord) - sectionCoord; return Mth.clamp(i, -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; } } } }