514 lines
22 KiB
Java
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;
|
|
|
|
}
|
|
}
|
|
|