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

Rewrite facade rendering #945

Open
wants to merge 3 commits into
base: dev/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
Original file line number Diff line number Diff line change
@@ -1,83 +1,105 @@
package com.enderio.conduits.client;

import com.enderio.conduits.client.model.conduit.facades.FacadeHelper;
import com.enderio.conduits.common.conduit.block.ConduitBundleBlock;
import com.enderio.conduits.common.conduit.block.ConduitBundleBlockEntity;
import com.mojang.blaze3d.vertex.VertexConsumer;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.Map;
import java.util.Set;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.core.BlockPos;
import net.minecraft.util.FastColor;
import net.minecraft.core.SectionPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.SingleThreadedRandomSource;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RenderLevelStageEvent;
import net.neoforged.neoforge.client.event.AddSectionGeometryEvent;
import net.neoforged.neoforge.client.model.data.ModelData;
import net.neoforged.neoforge.client.model.pipeline.VertexConsumerWrapper;

@EventBusSubscriber(bus = EventBusSubscriber.Bus.GAME, value = Dist.CLIENT)
public class ConduitFacadeRendering {

private static final ThreadLocal<RandomSource> RANDOM = ThreadLocal
.withInitial(() -> new SingleThreadedRandomSource(42L));

@SubscribeEvent
static void renderFacade(RenderLevelStageEvent event) {
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRIPWIRE_BLOCKS || FacadeHelper.areFacadesVisible()) {
static void renderFacade(AddSectionGeometryEvent event) {
Map<BlockPos, BlockState> facades = new Object2ObjectOpenHashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The map allocation should be moved below the if (blockList == null) check to avoid allocating a redundant map for sections without facades (i.e. most sections, including empty ones).


Set<BlockPos> blockList = ConduitBundleBlockEntity.CHUNK_FACADES
.getOrDefault(SectionPos.asLong(event.getSectionOrigin()), null);

if (blockList == null) {
return;
}
for (Map.Entry<BlockPos, BlockState> entry : ConduitBundleBlockEntity.FACADES.entrySet()) {
ClientLevel level = Minecraft.getInstance().level;
if (!level.isLoaded(entry.getKey())) {
return;
}
if (level.getBlockState(entry.getKey()).getBlock() instanceof ConduitBundleBlock) {
if (entry.getValue() == null) {
continue;
}

var baseConsumer = Minecraft.getInstance()
.renderBuffers()
.bufferSource()
.getBuffer(Sheets.translucentCullBlockSheet());
var wrappedConsumer = new VertexConsumerWrapper(baseConsumer) {
@Override
public VertexConsumer setColor(int r, int g, int b, int a) {
super.setColor(r, g, b, 85);
return this;
}
};

var cameraPos = event.getCamera().getPosition();
event.getPoseStack().pushPose();
event.getPoseStack()
.translate(entry.getKey().getX() - cameraPos.x, entry.getKey().getY() - cameraPos.y,
entry.getKey().getZ() - cameraPos.z);
for (BlockPos entry : blockList) {
facades.put(entry, ConduitBundleBlockEntity.FACADES.get(entry.asLong()));
}

if (facades.isEmpty())
return;

event.addRenderer(new FacadeRenderer(facades, FacadeHelper.areFacadesVisible()));
}

private static class FacadeRenderer implements AddSectionGeometryEvent.AdditionalSectionRenderer {
private final Map<BlockPos, BlockState> facades;
private final boolean opaque;

public FacadeRenderer(Map<BlockPos, BlockState> facades, boolean opaque) {
this.facades = facades;
this.opaque = opaque;
}

@Override
public void render(AddSectionGeometryEvent.SectionRenderingContext context) {
VertexConsumerWrapper wrapper = opaque ? null : new AlphaWrapper(context);

RandomSource random = RANDOM.get();

for (Map.Entry<BlockPos, BlockState> entry : facades.entrySet()) {
context.getPoseStack().pushPose();
context.getPoseStack()
.translate(entry.getKey().getX() & 15, entry.getKey().getY() & 15, entry.getKey().getZ() & 15);

var state = entry.getValue();
var pos = entry.getKey();

random.setSeed(42L);

var model = Minecraft.getInstance()
.getModelManager()
.getBlockModelShaper()
.getBlockModel(entry.getValue());
int color = Minecraft.getInstance().getBlockColors().getColor(entry.getValue(), level, entry.getKey());
for (var renderType : model.getRenderTypes(entry.getValue(), RandomSource.create(), ModelData.EMPTY)) {

for (var renderType : model.getRenderTypes(entry.getValue(), random, ModelData.EMPTY)) {
VertexConsumer consumer = wrapper == null ? context.getOrCreateChunkBuffer(renderType) : wrapper;
Minecraft.getInstance()
.getBlockRenderer()
.getModelRenderer()
.renderModel(event.getPoseStack().last(), wrappedConsumer, entry.getValue(), model,
FastColor.ARGB32.red(color) / 255.0F, FastColor.ARGB32.green(color) / 255.0F,
FastColor.ARGB32.blue(color) / 255.0F,
LightTexture.pack(level.getBrightness(LightLayer.BLOCK, entry.getKey()),
level.getBrightness(LightLayer.SKY, entry.getKey())),
OverlayTexture.NO_OVERLAY,
model.getModelData(level, entry.getKey(), entry.getValue(), ModelData.EMPTY),
renderType);
.tesselateBlock(context.getRegion(), model, state, pos, context.getPoseStack(), consumer,
true, random, 42L, OverlayTexture.NO_OVERLAY, ModelData.EMPTY, renderType);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here (and above as well) ModelData.EMPTY should not be used, but the correct ModelData should be fetched from the model and position. This is needed for some blocks to render properly.

}
Minecraft.getInstance().renderBuffers().bufferSource().endBatch(Sheets.translucentCullBlockSheet());
event.getPoseStack().popPose();

context.getPoseStack().popPose();
}
}

private static class AlphaWrapper extends VertexConsumerWrapper {
public AlphaWrapper(AddSectionGeometryEvent.SectionRenderingContext context) {
super(context.getOrCreateChunkBuffer(RenderType.translucent()));
}

@Override
public VertexConsumer setColor(int r, int g, int b, int a) {
super.setColor(r, g, b, 85);
return this;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import com.enderio.conduits.api.ConduitNode;
import com.enderio.conduits.api.facade.FacadeType;
import com.enderio.conduits.api.model.ConduitCoreModelModifier;
import com.enderio.conduits.client.ConduitFacadeColor;
import com.enderio.conduits.client.model.conduit.facades.FacadeHelper;
import com.enderio.conduits.client.model.conduit.modifier.ConduitCoreModelModifiers;
import com.enderio.conduits.common.Area;
Expand All @@ -34,7 +33,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType;
Expand All @@ -50,7 +48,6 @@
import net.minecraft.util.RandomSource;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.SingleThreadedRandomSource;
import net.neoforged.neoforge.client.ChunkRenderTypeSet;
Expand All @@ -71,23 +68,9 @@ public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction

List<BakedQuad> quads = new ArrayList<>();
ConduitBundle conduitBundle = extraData.get(ConduitBundleBlockEntity.BUNDLE_MODEL_PROPERTY);
ModelData data = extraData.get(ConduitBundleBlockEntity.FACADE_MODEL_DATA);

if (conduitBundle != null) {
if (FacadeHelper.areFacadesVisible()) {
IQuadTransformer transformer = quad -> quad.tintIndex = ConduitFacadeColor
.moveTintIndex(quad.getTintIndex());
Optional<Block> facadeOpt = conduitBundle.facade();
if (facadeOpt.isPresent()) {
BlockState facade = facadeOpt.get().defaultBlockState();
var model = Minecraft.getInstance().getBlockRenderer().getBlockModel(facade);
var facadeQuads = model.getQuads(facade, side, rand, data, renderType);

if (renderType != null && model.getRenderTypes(facade, rand, data).contains(renderType)) {
quads.addAll(transformer.process(facadeQuads));
}
}

// If the facade should hide the conduits, escape early.
if (conduitBundle.hasFacade()) {
boolean areConduitsHidden = conduitBundle.facadeType()
Expand Down Expand Up @@ -340,12 +323,7 @@ public ItemOverrides getOverrides() {
@Override
public ChunkRenderTypeSet getRenderTypes(@NotNull BlockState state, @NotNull RandomSource rand,
@NotNull ModelData data) {
ChunkRenderTypeSet facadeRenderTypes = data.get(ConduitBundleBlockEntity.FACADE_RENDERTYPE);
ChunkRenderTypeSet renderTypes = ChunkRenderTypeSet.of(RenderType.cutout());
if (facadeRenderTypes != null) {
renderTypes = ChunkRenderTypeSet.union(renderTypes, facadeRenderTypes);
}
return renderTypes;
return ChunkRenderTypeSet.of(RenderType.cutout());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be cached in a static field to avoid allocation.

}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
package com.enderio.conduits.client.model.conduit.facades;

import com.enderio.conduits.common.conduit.block.ConduitBundleBlockEntity;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.core.SectionPos;

// TODO: In future, support hiding specific conduit types too.
public class FacadeHelper {

private static boolean FACADES_VISIBLE = true;

public static void setFacadesVisible(boolean visible) {
if (visible != FACADES_VISIBLE) {
RenderSystem.recordRenderCall(() -> {
ConduitBundleBlockEntity.CHUNK_FACADES.keySet().forEach((section) -> {
Minecraft.getInstance().levelRenderer.setSectionDirty(SectionPos.x(section), SectionPos.y(section),
SectionPos.z(section));
});
});
}

FACADES_VISIBLE = visible;
}

public static boolean areFacadesVisible() {
return FACADES_VISIBLE;
}

public static void rebuildChunkMeshes() {
var minecraft = Minecraft.getInstance();

if (minecraft.levelRenderer.viewArea == null) {
return;
}

for (var section : minecraft.levelRenderer.viewArea.sections) {
section.setDirty(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ public static void onEquipmentChanged(LivingEquipmentChangeEvent event) {
ItemStack offHand = event.getEntity().getItemBySlot(EquipmentSlot.OFFHAND);
FacadeHelper.setFacadesVisible(
!mainHand.is(EIOTags.Items.HIDE_FACADES) && !offHand.is(EIOTags.Items.HIDE_FACADES));

FacadeHelper.rebuildChunkMeshes();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
import com.enderio.core.common.blockentity.EnderBlockEntity;
import dev.gigaherz.graph3.Graph;
import dev.gigaherz.graph3.GraphObject;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
Expand All @@ -43,6 +46,7 @@
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.SectionPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
Expand Down Expand Up @@ -79,7 +83,8 @@ public class ConduitBundleBlockEntity extends EnderBlockEntity {
public static final String CONDUIT_INV_KEY = "ConduitInv";

@UseOnly(LogicalSide.CLIENT)
public static final Map<BlockPos, BlockState> FACADES = new HashMap<>();
public static final Long2ObjectMap<BlockState> FACADES = new Long2ObjectOpenHashMap<>();
public static final Long2ObjectMap<Set<BlockPos>> CHUNK_FACADES = new Long2ObjectOpenHashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small thing, can this be a LongSet instead of Set<BlockPos>? We do need to convert it back to a blockpos, but it may be a bit faster?


private final ConduitShape shape = new ConduitShape();

Expand Down Expand Up @@ -120,9 +125,15 @@ public void updateClient() {
requestModelDataUpdate();
level.setBlocksDirty(getBlockPos(), Blocks.AIR.defaultBlockState(), getBlockState());
if (bundle.hasFacade()) {
FACADES.put(worldPosition, bundle.facade().get().defaultBlockState());
FACADES.put(worldPosition.asLong(), bundle.facade().get().defaultBlockState());
CHUNK_FACADES.computeIfAbsent(SectionPos.asLong(worldPosition), p -> new ObjectOpenHashSet<>())
.add(worldPosition);
} else {
FACADES.remove(worldPosition);
FACADES.remove(worldPosition.asLong());
Set<BlockPos> chunkList = CHUNK_FACADES.getOrDefault(SectionPos.asLong(worldPosition), null);
if (chunkList != null) {
chunkList.remove(worldPosition);
}
}
}
}
Expand Down Expand Up @@ -217,7 +228,8 @@ public void onChunkUnloaded() {
ConduitSavedData savedData = ConduitSavedData.get(serverLevel);
bundle.getConduits().forEach(type -> onChunkUnloaded(savedData, type));
} else {
FACADES.remove(worldPosition);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a heads up, I added another remove in this file a bit after your PR, so there's a small conflict you'll have to solve.

CHUNK_FACADES.remove(SectionPos.asLong(worldPosition));
FACADES.remove(worldPosition.asLong());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.enderio.conduits.mixin;

import com.enderio.conduits.common.conduit.block.ConduitBundleBlockEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;

@Mixin(BlockRenderDispatcher.class)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we don't have many mixins in enderio, do you mind writing a short explanation what this does/fixes? I think this is for the breaking overlay, but want to make sure.

public class BlockRenderDispatcherMixin {
@WrapOperation(method = "renderBreakingTexture(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/BlockAndTintGetter;Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/neoforged/neoforge/client/model/data/ModelData;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/block/BlockModelShaper;getBlockModel(Lnet/minecraft/world/level/block/state/BlockState;)Lnet/minecraft/client/resources/model/BakedModel;"))
public BakedModel enderio$checkFacades(BlockModelShaper instance, BlockState state, Operation<BakedModel> original,
BlockState localState, BlockPos pos, BlockAndTintGetter level) {
BlockState facadeState = ConduitBundleBlockEntity.FACADES.getOrDefault(pos.asLong(), null);

return original.call(instance, facadeState == null ? state : facadeState);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ description="The conduits module for Ender IO"
displayURL="https://enderio.com/"
logoFile="logo.png"

[[mixins]]
config="enderioconduits.mixins.json"

[[dependencies.enderio_conduits]]
modId="minecraft"
type="required"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"required" : true,
"package" : "com.enderio.conduits.mixin",
"compatibilityLevel" : "JAVA_17",
"client" : [
"BlockRenderDispatcherMixin"
],
"minVersion" : "0.8"
}
Loading