2025-11-24 22:52:51 +03:00

514 lines
22 KiB
Java

/*
* 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<Runnable> toUpload = Queues.newConcurrentLinkedQueue();
private final Executor mainThreadUploadExecutor = this.toUpload::add;
private final Queue<SectionMesh> 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> sectionMesh = new AtomicReference<SectionMesh>(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<Void> upload(Map<ChunkSectionLayer, MeshData> 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<Void> 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<SectionTaskResult> 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<Void> 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<SectionTaskResult> 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<SectionTaskResult> 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<Void> 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;
}
}