Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.21.1] Feature: Add cached ber rendering pipeline #1816

Open
wants to merge 6 commits into
base: 1.21.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions patches/net/minecraft/client/Minecraft.java.patch
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,14 @@
if (integratedserver != null) {
this.profiler.push("waitForServer");

@@ -2175,6 +_,7 @@
}

private void updateLevelInEngines(@Nullable ClientLevel p_91325_) {
+ net.neoforged.neoforge.client.ClientHooks.onUpdateLevel(p_91325_);
this.levelRenderer.setLevel(p_91325_);
this.particleEngine.setLevel(p_91325_);
this.blockEntityRenderDispatcher.setLevel(p_91325_);
@@ -2258,6 +_,7 @@

private void pickBlock() {
Expand Down
18 changes: 14 additions & 4 deletions patches/net/minecraft/client/renderer/LevelRenderer.java.patch
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
float f = this.minecraft.level.getRainLevel(1.0F) / (Minecraft.useFancyGraphics() ? 1.0F : 2.0F);
if (!(f <= 0.0F)) {
RandomSource randomsource = RandomSource.create((long)this.ticks * 312987231L);
@@ -942,9 +_,11 @@
@@ -942,18 +_,23 @@
RenderSystem.clear(16640, Minecraft.ON_OSX);
float f1 = p_109605_.getRenderDistance();
boolean flag1 = this.minecraft.level.effects().isFoggyAt(Mth.floor(d0), Mth.floor(d1)) || this.minecraft.gui.getBossOverlay().shouldCreateWorldFog();
Expand All @@ -41,7 +41,9 @@
profilerfiller.popPush("fog");
FogRenderer.setupFog(p_109604_, FogRenderer.FogMode.FOG_TERRAIN, Math.max(f1, 32.0F), flag1, f);
profilerfiller.popPush("terrain_setup");
@@ -953,7 +_,9 @@
this.setupRender(p_109604_, frustum, flag, this.minecraft.player.isSpectator());
profilerfiller.popPush("compile_sections");
+ net.neoforged.neoforge.client.ClientHooks.onCompileSectionsPre();
this.compileSections(p_109604_);
profilerfiller.popPush("terrain");
this.renderSectionLayer(RenderType.solid(), d0, d1, d2, p_254120_, p_323920_);
Expand Down Expand Up @@ -70,13 +72,13 @@
multibuffersource = multibuffersource$buffersource;
}

@@ -1027,12 +_,14 @@
@@ -1027,12 +_,13 @@
multibuffersource$buffersource.endBatch(RenderType.entityCutout(TextureAtlas.LOCATION_BLOCKS));
multibuffersource$buffersource.endBatch(RenderType.entityCutoutNoCull(TextureAtlas.LOCATION_BLOCKS));
multibuffersource$buffersource.endBatch(RenderType.entitySmoothCutout(TextureAtlas.LOCATION_BLOCKS));
+ net.neoforged.neoforge.client.ClientHooks.dispatchRenderStage(net.neoforged.neoforge.client.event.RenderLevelStageEvent.Stage.AFTER_ENTITIES, this, posestack, p_254120_, p_323920_, this.ticks, p_109604_, frustum);
profilerfiller.popPush("blockentities");
-
for (SectionRenderDispatcher.RenderSection sectionrenderdispatcher$rendersection : this.visibleSections) {
List<BlockEntity> list = sectionrenderdispatcher$rendersection.getCompiled().getRenderableBlockEntities();
if (!list.isEmpty()) {
Expand Down Expand Up @@ -153,6 +155,14 @@
}

this.minecraft.debugRenderer.render(posestack, multibuffersource$buffersource, d0, d1, d2);
@@ -1133,6 +_,7 @@
multibuffersource$buffersource.endBatch(RenderType.entityGlint());
multibuffersource$buffersource.endBatch(RenderType.entityGlintDirect());
multibuffersource$buffersource.endBatch(RenderType.waterMask());
+ net.neoforged.neoforge.client.ClientHooks.onRenderTranslucentPre(p_254120_, p_323920_);
this.renderBuffers.crumblingBufferSource().endBatch();
if (this.transparencyChain != null) {
multibuffersource$buffersource.endBatch(RenderType.lines());
@@ -1147,9 +_,13 @@
this.particlesTarget.copyDepthFrom(this.minecraft.getMainRenderTarget());
RenderStateShard.PARTICLES_TARGET.setupRenderState();
Expand Down
10 changes: 9 additions & 1 deletion patches/net/minecraft/world/level/chunk/LevelChunk.java.patch
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,15 @@
} else {
CompoundTag compoundtag = this.pendingBlockEntities.get(p_62932_);
if (compoundtag != null) {
@@ -436,6 +_,7 @@
@@ -431,11 +_,15 @@
if (this.isInLevel()) {
BlockEntity blockentity = this.blockEntities.remove(p_62919_);
if (blockentity != null) {
+ if (this.level.isClientSide) {
+ net.neoforged.neoforge.client.ClientHooks.onBlockEntityRemoved(blockentity);
+ }
if (this.level instanceof ServerLevel serverlevel) {
this.removeGameEventListener(blockentity, serverlevel);
}

blockentity.setRemoved();
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/net/neoforged/neoforge/client/ClientHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
import net.neoforged.fml.ModLoader;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.common.asm.enumextension.ExtensionInfo;
import net.neoforged.neoforge.client.block.CacheableBERenderingPipeline;
import net.neoforged.neoforge.client.entity.animation.json.AnimationTypeManager;
import net.neoforged.neoforge.client.event.AddSectionGeometryEvent;
import net.neoforged.neoforge.client.event.CalculateDetachedCameraDistanceEvent;
Expand Down Expand Up @@ -791,6 +792,22 @@ public static void registerShaders(RegisterShadersEvent event) throws IOExceptio
}
}

public static void onBlockEntityRemoved(BlockEntity blockEntity) {
CacheableBERenderingPipeline.getInstance().blockRemoved(blockEntity);
}

public static void onRenderTranslucentPre(Matrix4f frustumMatrix, Matrix4f projectionMatrix) {
CacheableBERenderingPipeline.getInstance().render(frustumMatrix, projectionMatrix);
}

public static void onCompileSectionsPre() {
CacheableBERenderingPipeline.getInstance().runTasks();
}

public static void onUpdateLevel(ClientLevel level) {
CacheableBERenderingPipeline.updateLevel(level);
}

public static Font getTooltipFont(ItemStack stack, Font fallbackFont) {
Font stackFont = IClientItemExtensions.of(stack).getFont(stack, IClientItemExtensions.FontContext.TOOLTIP);
return stackFont == null ? fallbackFont : stackFont;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client;

import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
import com.mojang.blaze3d.vertex.MeshData;
import com.mojang.blaze3d.vertex.VertexBuffer;
import com.mojang.blaze3d.vertex.VertexConsumer;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;

@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
public class FullyBufferedBufferSource extends MultiBufferSource.BufferSource implements AutoCloseable {
private final Map<RenderType, ByteBufferBuilder> byteBuffers = new HashMap<>();
private final Map<RenderType, BufferBuilder> bufferBuilders = new HashMap<>();
private final Reference2IntMap<RenderType> indexCountMap = new Reference2IntOpenHashMap<>();
private final Map<RenderType, MeshData.SortState> meshSorts = new HashMap<>();

public FullyBufferedBufferSource() {
super(null, null);
}

private ByteBufferBuilder getByteBuffer(RenderType renderType) {
return byteBuffers.computeIfAbsent(renderType, it -> new ByteBufferBuilder(786432));
}

@Override
public VertexConsumer getBuffer(RenderType renderType) {
return bufferBuilders.computeIfAbsent(
renderType,
it -> new BufferBuilder(getByteBuffer(it), it.mode, it.format));
}

public boolean isEmpty() {
return !bufferBuilders.isEmpty() && bufferBuilders.values().stream().noneMatch(it -> it.vertices > 0);
}

@Override
public void endBatch(RenderType renderType) {}

@Override
public void endLastBatch() {}

@Override
public void endBatch() {}

public void upload(
Function<RenderType, VertexBuffer> vertexBufferGetter,
Function<RenderType, ByteBufferBuilder> byteBufferSupplier,
Consumer<Runnable> runner) {
for (RenderType renderType : bufferBuilders.keySet()) {
runner.accept(() -> {
BufferBuilder bufferBuilder = bufferBuilders.get(renderType);
ByteBufferBuilder byteBuffer = byteBuffers.get(renderType);
int compiledVertices = bufferBuilder.vertices * renderType.format.getVertexSize();
if (compiledVertices >= 0) {
MeshData mesh = bufferBuilder.build();
indexCountMap.put(renderType, renderType.mode.indexCount(bufferBuilder.vertices));
if (mesh != null) {
if (renderType.sortOnUpload) {
MeshData.SortState sortState = mesh.sortQuads(
byteBufferSupplier.apply(renderType),
RenderSystem.getVertexSorting());
meshSorts.put(
renderType,
sortState);
}
VertexBuffer vertexBuffer = vertexBufferGetter.apply(renderType);
vertexBuffer.bind();
vertexBuffer.upload(mesh);
VertexBuffer.unbind();
}
}
byteBuffer.close();
bufferBuilders.remove(renderType);
byteBuffers.remove(renderType);
});
}
}

public void close(RenderType renderType) {
ByteBufferBuilder builder = byteBuffers.get(renderType);
builder.close();
}

public Reference2IntMap<RenderType> getIndexCountMap() {
return indexCountMap;
}

public Map<RenderType, MeshData.SortState> getMeshSorts() {
return meshSorts;
}

public void close() {
byteBuffers.keySet().forEach(this::close);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client.block;

import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.neoforged.neoforge.client.extensions.IBlockEntityRendererExtension;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;

public class CacheableBERenderingPipeline {
@Nullable
private static CacheableBERenderingPipeline instance;
private final ClientLevel level;
private final Queue<Runnable> pendingCompiles = new ArrayDeque<>();
private final Queue<Runnable> pendingUploads = new ArrayDeque<>();
private final Map<ChunkPos, CachedRegion> regions = new HashMap<>();
private boolean valid = true;

public CachedRegion getRenderRegion(ChunkPos chunkPos) {
if (regions.containsKey(chunkPos)) {
return regions.get(chunkPos);
}
CachedRegion region = new CachedRegion(chunkPos, this);
regions.put(chunkPos, region);
return region;
}

public CacheableBERenderingPipeline(ClientLevel level) {
this.level = level;
}

public void runTasks() {
while (!pendingCompiles.isEmpty() && valid) {
pendingCompiles.poll().run();
}
while (!pendingUploads.isEmpty() && valid) {
pendingUploads.poll().run();
}
}

/**
* Updates the rendering pipeline instance with a new level context.
*
* @param level The new ClientLevel instance that the rendering pipeline should be updated to use.
*/
public static void updateLevel(ClientLevel level) {
if (instance != null) {
instance.releaseBuffers();
}
instance = new CacheableBERenderingPipeline(level);
}

/**
* Notifies the pipeline that a {@link BlockEntity} has been removed.
* This method will be automatically called when a {@link BlockEntity} has been removed.
*
* @param be The removed {@link BlockEntity}
*/
public void blockRemoved(BlockEntity be) {
IBlockEntityRendererExtension<?> renderer = Minecraft.getInstance()
.getBlockEntityRenderDispatcher()
.getRenderer(be);
if (renderer == null) return;
ChunkPos chunkPos = new ChunkPos(be.getBlockPos());
getRenderRegion(chunkPos).blockRemoved(be);
}

/**
* Notifies the pipeline that a {@link BlockEntity} has been updated and the cache should be rebuilt.
*
* @param be The updated {@link BlockEntity}
*/
public void update(BlockEntity be) {
BlockEntityRenderer<?> renderer = Minecraft.getInstance()
.getBlockEntityRenderDispatcher()
.getRenderer(be);
if (renderer == null) return;
ChunkPos chunkPos = new ChunkPos(be.getBlockPos());
getRenderRegion(chunkPos).update(be);
}

public void submitUploadTask(Runnable task) {
pendingUploads.add(task);
}

public void submitCompileTask(Runnable task) {
pendingCompiles.add(task);
}

/**
* Releases all buffers in use and mark current pipeline instance as invalid.
*/
public void releaseBuffers() {
regions.values().forEach(CachedRegion::releaseBuffers);
valid = false;
}

public void render(Matrix4f frustumMatrix, Matrix4f projectionMatrix) {
regions.values().forEach(it -> it.render(frustumMatrix, projectionMatrix));
}

/**
* Retrieves the current instance of the CacheableBERenderingPipeline.
*
* @return The current instance of the CacheableBERenderingPipeline,
* or null if there has no {@link ClientLevel} in current {@link Minecraft} client.
*/
@Nullable
public static CacheableBERenderingPipeline getInstance() {
return instance;
}
}
Loading
Loading