/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.Queues * org.jspecify.annotations.Nullable */ 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.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.client.renderer.chunk.ChunkSectionLayer; import net.minecraft.client.renderer.chunk.CompileTaskDynamicQueue; import net.minecraft.client.renderer.chunk.CompiledSectionMesh; import net.minecraft.client.renderer.chunk.RenderRegionCache; import net.minecraft.client.renderer.chunk.RenderSectionRegion; import net.minecraft.client.renderer.chunk.SectionCompiler; import net.minecraft.client.renderer.chunk.SectionMesh; import net.minecraft.client.renderer.chunk.TranslucencyPointOfView; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.SectionPos; import net.minecraft.util.Util; 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.jspecify.annotations.Nullable; public class SectionRenderDispatcher { private final CompileTaskDynamicQueue compileQueue = new CompileTaskDynamicQueue(); private final Queue toUpload = Queues.newConcurrentLinkedQueue(); private final Executor mainThreadUploadExecutor = this.toUpload::add; private final Queue toClose = Queues.newConcurrentLinkedQueue(); private final SectionBufferBuilderPack fixedBuffers; private final SectionBufferBuilderPool bufferPool; private volatile boolean closed; private final ConsecutiveExecutor consecutiveExecutor; private final TracingExecutor executor; private ClientLevel level; private final LevelRenderer renderer; private Vec3 cameraPosition = Vec3.ZERO; private final SectionCompiler sectionCompiler; public SectionRenderDispatcher(ClientLevel level, LevelRenderer renderer, TracingExecutor executor, RenderBuffers renderBuffers, BlockRenderDispatcher blockRenderer, BlockEntityRenderDispatcher blockEntityRenderDispatcher) { this.level = level; this.renderer = renderer; this.fixedBuffers = renderBuffers.fixedBufferPack(); this.bufferPool = renderBuffers.sectionBufferPool(); this.executor = executor; this.consecutiveExecutor = new ConsecutiveExecutor(executor, "Section Renderer"); this.consecutiveExecutor.schedule(this::runTask); this.sectionCompiler = new SectionCompiler(blockRenderer, blockEntityRenderDispatcher); } public void setLevel(ClientLevel level) { this.level = level; } private void runTask() { if (this.closed || this.bufferPool.isEmpty()) { return; } RenderSection.CompileTask task = this.compileQueue.poll(this.cameraPosition); if (task == null) { return; } SectionBufferBuilderPack buffer = Objects.requireNonNull(this.bufferPool.acquire()); ((CompletableFuture)CompletableFuture.supplyAsync(() -> task.doTask(buffer), this.executor.forName(task.name())).thenCompose(f -> f)).whenComplete((result, throwable) -> { if (throwable != null) { Minecraft.getInstance().delayCrash(CrashReport.forThrowable(throwable, "Batching sections")); return; } task.isCompleted.set(true); this.consecutiveExecutor.schedule(() -> { if (result == SectionTaskResult.SUCCESSFUL) { buffer.clearAll(); } else { buffer.discardAll(); } this.bufferPool.release(buffer); this.runTask(); }); }); } public void setCameraPosition(Vec3 cameraPosition) { this.cameraPosition = cameraPosition; } public void uploadAllPendingUploads() { SectionMesh mesh; Runnable upload; while ((upload = this.toUpload.poll()) != null) { upload.run(); } while ((mesh = this.toClose.poll()) != null) { mesh.close(); } } public void rebuildSectionSync(RenderSection section, RenderRegionCache cache) { section.compileSync(cache); } public void schedule(RenderSection.CompileTask task) { if (this.closed) { return; } this.consecutiveExecutor.schedule(() -> { if (this.closed) { return; } 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(); } public class RenderSection { public static final int SIZE = 16; public final int index; public final AtomicReference sectionMesh = new AtomicReference(CompiledSectionMesh.UNCOMPILED); private @Nullable RebuildTask lastRebuildTask; private @Nullable ResortTransparencyTask lastResortTransparencyTask; private AABB bb; private boolean dirty = true; private volatile long sectionNode = SectionPos.asLong(-1, -1, -1); private final BlockPos.MutableBlockPos renderOrigin = new BlockPos.MutableBlockPos(-1, -1, -1); private boolean playerChanged; private long uploadedTime; private long fadeDuration; private boolean wasPreviouslyEmpty; public RenderSection(int index, long sectionNode) { this.index = index; this.setSectionNode(sectionNode); } public float getVisibility(long now) { long elapsed = now - this.uploadedTime; if (elapsed >= this.fadeDuration) { return 1.0f; } return (float)elapsed / (float)this.fadeDuration; } public void setFadeDuration(long fadeDuration) { this.fadeDuration = fadeDuration; } public void setWasPreviouslyEmpty(boolean wasPreviouslyEmpty) { this.wasPreviouslyEmpty = wasPreviouslyEmpty; } public boolean wasPreviouslyEmpty() { return this.wasPreviouslyEmpty; } private boolean doesChunkExistAt(long sectionNode) { ChunkAccess chunk = SectionRenderDispatcher.this.level.getChunk(SectionPos.x(sectionNode), SectionPos.z(sectionNode), ChunkStatus.FULL, false); return chunk != null && SectionRenderDispatcher.this.level.getLightEngine().lightOnInColumn(SectionPos.getZeroNode(sectionNode)); } 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 upload(Map renderedLayers, CompiledSectionMesh compiledSectionMesh) { if (SectionRenderDispatcher.this.closed) { renderedLayers.values().forEach(MeshData::close); return CompletableFuture.completedFuture(null); } return CompletableFuture.runAsync(() -> renderedLayers.forEach((layer, mesh) -> { try (Zone ignored = Profiler.get().zone("Upload Section Layer");){ compiledSectionMesh.uploadMeshLayer((ChunkSectionLayer)((Object)((Object)layer)), (MeshData)mesh, this.sectionNode); mesh.close(); } if (this.uploadedTime == 0L) { this.uploadedTime = Util.getMillis(); } }), SectionRenderDispatcher.this.mainThreadUploadExecutor); } public CompletableFuture uploadSectionIndexBuffer(CompiledSectionMesh compiledSectionMesh, ByteBufferBuilder.Result indexBuffer, ChunkSectionLayer layer) { if (SectionRenderDispatcher.this.closed) { indexBuffer.close(); return CompletableFuture.completedFuture(null); } return CompletableFuture.runAsync(() -> { try (Zone ignored = Profiler.get().zone("Upload Section Indices");){ compiledSectionMesh.uploadLayerIndexBuffer(layer, indexBuffer, this.sectionNode); indexBuffer.close(); } }, SectionRenderDispatcher.this.mainThreadUploadExecutor); } public void setSectionNode(long sectionNode) { this.reset(); this.sectionNode = sectionNode; int x = SectionPos.sectionToBlockCoord(SectionPos.x(sectionNode)); int y = SectionPos.sectionToBlockCoord(SectionPos.y(sectionNode)); int z = SectionPos.sectionToBlockCoord(SectionPos.z(sectionNode)); this.renderOrigin.set(x, y, z); this.bb = new AABB(x, y, z, x + 16, y + 16, z + 16); } public SectionMesh getSectionMesh() { return this.sectionMesh.get(); } public void reset() { this.cancelTasks(); this.sectionMesh.getAndSet(CompiledSectionMesh.UNCOMPILED).close(); this.dirty = true; this.uploadedTime = 0L; this.wasPreviouslyEmpty = false; } public BlockPos getRenderOrigin() { return this.renderOrigin; } public long getSectionNode() { return this.sectionNode; } public void setDirty(boolean fromPlayer) { boolean wasDirty = this.dirty; this.dirty = true; this.playerChanged = fromPlayer | (wasDirty && 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) { SectionMesh sectionMesh = this.getSectionMesh(); if (sectionMesh instanceof CompiledSectionMesh) { CompiledSectionMesh mesh = (CompiledSectionMesh)sectionMesh; this.lastResortTransparencyTask = new ResortTransparencyTask(mesh); 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 CompileTask createCompileTask(RenderRegionCache cache) { this.cancelTasks(); RenderSectionRegion region = cache.createRegion(SectionRenderDispatcher.this.level, this.sectionNode); boolean isRecompile = this.sectionMesh.get() != CompiledSectionMesh.UNCOMPILED; this.lastRebuildTask = new RebuildTask(region, isRecompile); return this.lastRebuildTask; } public void rebuildSectionAsync(RenderRegionCache cache) { CompileTask task = this.createCompileTask(cache); SectionRenderDispatcher.this.schedule(task); } public void compileSync(RenderRegionCache cache) { CompileTask task = this.createCompileTask(cache); task.doTask(SectionRenderDispatcher.this.fixedBuffers); } private void setSectionMesh(SectionMesh sectionMesh) { SectionMesh oldMesh = this.sectionMesh.getAndSet(sectionMesh); SectionRenderDispatcher.this.toClose.add(oldMesh); SectionRenderDispatcher.this.renderer.addRecentlyCompiledSection(this); } private VertexSorting createVertexSorting(SectionPos sectionPos) { Vec3 camera = SectionRenderDispatcher.this.cameraPosition; return VertexSorting.byDistance((float)(camera.x - (double)sectionPos.minBlockX()), (float)(camera.y - (double)sectionPos.minBlockY()), (float)(camera.z - (double)sectionPos.minBlockZ())); } private class ResortTransparencyTask extends CompileTask { private final CompiledSectionMesh compiledSectionMesh; public ResortTransparencyTask(CompiledSectionMesh compiledSectionMesh) { super(true); this.compiledSectionMesh = compiledSectionMesh; } @Override protected String name() { return "rend_chk_sort"; } @Override public CompletableFuture doTask(SectionBufferBuilderPack buffers) { if (this.isCancelled.get()) { return CompletableFuture.completedFuture(SectionTaskResult.CANCELLED); } MeshData.SortState state = this.compiledSectionMesh.getTransparencyState(); if (state == null || this.compiledSectionMesh.isEmpty(ChunkSectionLayer.TRANSLUCENT)) { return CompletableFuture.completedFuture(SectionTaskResult.CANCELLED); } long sectionNode = RenderSection.this.sectionNode; VertexSorting vertexSorting = RenderSection.this.createVertexSorting(SectionPos.of(sectionNode)); TranslucencyPointOfView translucencyPointOfView = TranslucencyPointOfView.of(SectionRenderDispatcher.this.cameraPosition, sectionNode); if (!this.compiledSectionMesh.isDifferentPointOfView(translucencyPointOfView) && !translucencyPointOfView.isAxisAligned()) { return CompletableFuture.completedFuture(SectionTaskResult.CANCELLED); } ByteBufferBuilder.Result indexBuffer = state.buildSortedIndexBuffer(buffers.buffer(ChunkSectionLayer.TRANSLUCENT), vertexSorting); if (indexBuffer == null) { return CompletableFuture.completedFuture(SectionTaskResult.CANCELLED); } if (this.isCancelled.get()) { indexBuffer.close(); return CompletableFuture.completedFuture(SectionTaskResult.CANCELLED); } CompletableFuture future = RenderSection.this.uploadSectionIndexBuffer(this.compiledSectionMesh, indexBuffer, ChunkSectionLayer.TRANSLUCENT); return future.handle((ignored, throwable) -> { if (throwable != null && !(throwable instanceof CancellationException) && !(throwable instanceof InterruptedException)) { Minecraft.getInstance().delayCrash(CrashReport.forThrowable(throwable, "Rendering section")); } if (this.isCancelled.get()) { return SectionTaskResult.CANCELLED; } this.compiledSectionMesh.setTranslucencyPointOfView(translucencyPointOfView); return SectionTaskResult.SUCCESSFUL; }); } @Override public void cancel() { this.isCancelled.set(true); } } public abstract class CompileTask { protected final AtomicBoolean isCancelled = new AtomicBoolean(false); protected final AtomicBoolean isCompleted = new AtomicBoolean(false); protected final boolean isRecompile; public CompileTask(boolean isRecompile) { this.isRecompile = isRecompile; } public abstract CompletableFuture doTask(SectionBufferBuilderPack var1); public abstract void cancel(); protected abstract String name(); public boolean isRecompile() { return this.isRecompile; } public BlockPos getRenderOrigin() { return RenderSection.this.renderOrigin; } } private class RebuildTask extends CompileTask { protected final RenderSectionRegion region; public RebuildTask(RenderSectionRegion region, boolean isRecompile) { super(isRecompile); this.region = region; } @Override protected String name() { return "rend_chk_rebuild"; } @Override public CompletableFuture doTask(SectionBufferBuilderPack buffers) { SectionCompiler.Results results; if (this.isCancelled.get()) { return CompletableFuture.completedFuture(SectionTaskResult.CANCELLED); } long sectionNode = RenderSection.this.sectionNode; SectionPos sectionPos = SectionPos.of(sectionNode); if (this.isCancelled.get()) { return CompletableFuture.completedFuture(SectionTaskResult.CANCELLED); } try (Zone ignored2 = Profiler.get().zone("Compile Section");){ results = SectionRenderDispatcher.this.sectionCompiler.compile(sectionPos, this.region, RenderSection.this.createVertexSorting(sectionPos), buffers); } TranslucencyPointOfView translucencyPointOfView = TranslucencyPointOfView.of(SectionRenderDispatcher.this.cameraPosition, sectionNode); if (this.isCancelled.get()) { results.release(); return CompletableFuture.completedFuture(SectionTaskResult.CANCELLED); } CompiledSectionMesh compiledSectionMesh = new CompiledSectionMesh(translucencyPointOfView, results); CompletableFuture uploadFuture = RenderSection.this.upload(results.renderedLayers, compiledSectionMesh); return uploadFuture.handle((ignored, 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) { SectionRenderDispatcher.this.toClose.add(compiledSectionMesh); return SectionTaskResult.CANCELLED; } RenderSection.this.setSectionMesh(compiledSectionMesh); return SectionTaskResult.SUCCESSFUL; }); } @Override public void cancel() { if (this.isCancelled.compareAndSet(false, true)) { RenderSection.this.setDirty(false); } } } } private static enum SectionTaskResult { SUCCESSFUL, CANCELLED; } }