minecraft-src/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java
2025-07-04 03:45:38 +03:00

656 lines
22 KiB
Java

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<Runnable> toUpload = Queues.<Runnable>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<RenderType> hasBlocks = new ObjectArraySet<>(RenderType.chunkBufferLayers().size());
final List<BlockEntity> renderableBlockEntities = Lists.<BlockEntity>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<BlockEntity> 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<SectionRenderDispatcher.CompiledSection> compiled = new AtomicReference(SectionRenderDispatcher.CompiledSection.UNCOMPILED);
public final AtomicReference<SectionRenderDispatcher.TranslucencyPointOfView> pointOfView = new AtomicReference(null);
@Nullable
private RebuildTask lastRebuildTask;
@Nullable
private ResortTransparencyTask lastResortTransparencyTask;
private final Set<BlockEntity> globalBlockEntities = Sets.<BlockEntity>newHashSet();
private final Map<RenderType, SectionRenderDispatcher.SectionBuffers> 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<Void> 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<Void> 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<BlockEntity> blockEntities) {
Set<BlockEntity> set = Sets.<BlockEntity>newHashSet(blockEntities);
Set<BlockEntity> set2;
synchronized (this.globalBlockEntities) {
set2 = Sets.<BlockEntity>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;
}
}
}
}