First try

- Fix race condition causing instancers to never be initialized
- Fix frame uniforms being 8 bytes too small
- Don't have any bias in BakedModelBufferer#bufferItem, instead sort
  meshes with glint transparency to be rendered last
- Only visualized item entities if their items are supported
- Key item models directly on the BakedModel + if the stack has foil
This commit is contained in:
Jozufozu 2024-04-09 21:49:02 -07:00
parent 163649e792
commit b51ccd43e2
6 changed files with 86 additions and 60 deletions

View file

@ -4,7 +4,9 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.backend.Engine; import com.jozufozu.flywheel.api.backend.Engine;
@ -32,7 +34,7 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
* <br> * <br>
* All new instancers land here before having resources allocated in {@link #flush}. * All new instancers land here before having resources allocated in {@link #flush}.
*/ */
protected final List<UninitializedInstancer<N, ?>> initializationQueue = new ArrayList<>(); protected final Queue<UninitializedInstancer<N, ?>> initializationQueue = new ConcurrentLinkedQueue<>();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, RenderStage stage) { public <I extends Instance> Instancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, RenderStage stage) {
@ -72,7 +74,8 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
// Only queue the instancer for initialization if it has anything to render. // Only queue the instancer for initialization if it has anything to render.
if (checkAndWarnEmptyModel(key.model())) { if (checkAndWarnEmptyModel(key.model())) {
// Thread safety: this method is called atomically from within computeIfAbsent, // Thread safety: this method is called atomically from within computeIfAbsent,
// so we don't need extra synchronization to protect the queue. // so you'd think we don't need extra synchronization to protect the queue, but
// somehow threads can race here and wind up never initializing an instancer.
initializationQueue.add(new UninitializedInstancer<>(key, out)); initializationQueue.add(new UninitializedInstancer<>(key, out));
} }
return out; return out;

View file

@ -19,7 +19,7 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
public class FrameUniforms implements UniformProvider { public class FrameUniforms implements UniformProvider {
public static final int SIZE = 800; public static final int SIZE = 808;
public int debugMode; public int debugMode;
@Nullable @Nullable

View file

@ -1,8 +1,6 @@
package com.jozufozu.flywheel.lib.model.baked; package com.jozufozu.flywheel.lib.model.baked;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
@ -14,6 +12,8 @@ import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.blaze3d.vertex.VertexConsumer;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceArraySet;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.MultiBufferSource;
@ -150,10 +150,9 @@ final class BakedModelBufferer {
var emitterSource = objects.emitterSource; var emitterSource = objects.emitterSource;
emitterSource.resultConsumer(consumer); emitterSource.resultConsumer(consumer);
var itemRenderer = Minecraft.getInstance() Minecraft.getInstance()
.getItemRenderer(); .getItemRenderer()
.render(stack, displayContext, leftHand, poseStack, emitterSource, 0, OverlayTexture.NO_OVERLAY, model);
itemRenderer.render(stack, displayContext, leftHand, poseStack, emitterSource, 0, OverlayTexture.NO_OVERLAY, model);
emitterSource.end(); emitterSource.end();
} }
@ -182,11 +181,8 @@ final class BakedModelBufferer {
} }
private static class MeshEmitterSource implements MultiBufferSource { private static class MeshEmitterSource implements MultiBufferSource {
private final Map<RenderType, MeshEmitter> emitters = new HashMap<>(); private final Map<RenderType, MeshEmitter> emitters = new Reference2ReferenceOpenHashMap<>();
private final Set<MeshEmitter> active = new ReferenceArraySet<>();
private final Set<MeshEmitter> active = new LinkedHashSet<>();
// Hack: we want glint to render after everything else so track it separately here
private final Set<MeshEmitter> activeGlint = new LinkedHashSet<>();
@Nullable @Nullable
private ResultConsumer resultConsumer; private ResultConsumer resultConsumer;
@ -195,13 +191,6 @@ final class BakedModelBufferer {
public VertexConsumer getBuffer(RenderType renderType) { public VertexConsumer getBuffer(RenderType renderType) {
var out = emitters.computeIfAbsent(renderType, type -> new MeshEmitter(new BufferBuilder(type.bufferSize()), type)); var out = emitters.computeIfAbsent(renderType, type -> new MeshEmitter(new BufferBuilder(type.bufferSize()), type));
Set<MeshEmitter> active;
if (renderType == RenderType.glint() || renderType == RenderType.glintDirect() || renderType == RenderType.entityGlint() || renderType == RenderType.entityGlintDirect()) {
active = this.activeGlint;
} else {
active = this.active;
}
if (active.add(out)) { if (active.add(out)) {
out.begin(resultConsumer); out.begin(resultConsumer);
} }
@ -214,12 +203,7 @@ final class BakedModelBufferer {
emitter.end(); emitter.end();
} }
for (var emitter : activeGlint) {
emitter.end();
}
active.clear(); active.clear();
activeGlint.clear();
resultConsumer = null; resultConsumer = null;
} }

View file

@ -1,11 +1,14 @@
package com.jozufozu.flywheel.lib.model.baked; package com.jozufozu.flywheel.lib.model.baked;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.material.Transparency;
import com.jozufozu.flywheel.api.model.Model; import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.vertex.VertexView; import com.jozufozu.flywheel.api.vertex.VertexView;
import com.jozufozu.flywheel.lib.memory.MemoryBlock; import com.jozufozu.flywheel.lib.memory.MemoryBlock;
@ -16,24 +19,35 @@ import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer;
import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView; import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
public class ItemModelBuilder { public class ItemModelBuilder {
public static final Comparator<Model.ConfiguredMesh> GLINT_LAST = (a, b) -> {
if (a.material()
.transparency() == b.material()
.transparency()) {
return 0;
}
return a.material()
.transparency() == Transparency.GLINT ? 1 : -1;
};
private final ItemStack itemStack; private final ItemStack itemStack;
private final BakedModel model;
@Nullable @Nullable
private PoseStack poseStack; private PoseStack poseStack;
@Nullable @Nullable
private ItemDisplayContext displayContext; private ItemDisplayContext displayContext;
private boolean leftHand; private boolean leftHand;
private int seed = 42;
@Nullable @Nullable
private BiFunction<RenderType, Boolean, Material> materialFunc; private BiFunction<RenderType, Boolean, Material> materialFunc;
public ItemModelBuilder(ItemStack itemStack) { public ItemModelBuilder(ItemStack itemStack, BakedModel model) {
this.itemStack = itemStack; this.itemStack = itemStack;
this.model = model;
} }
public ItemModelBuilder poseStack(PoseStack poseStack) { public ItemModelBuilder poseStack(PoseStack poseStack) {
@ -51,11 +65,6 @@ public class ItemModelBuilder {
return this; return this;
} }
public ItemModelBuilder seed(int seed) {
this.seed = seed;
return this;
}
public ItemModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) { public ItemModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
this.materialFunc = materialFunc; this.materialFunc = materialFunc;
return this; return this;
@ -69,7 +78,7 @@ public class ItemModelBuilder {
materialFunc = ModelUtil::getItemMaterial; materialFunc = ModelUtil::getItemMaterial;
} }
var out = ImmutableList.<Model.ConfiguredMesh>builder(); ArrayList<Model.ConfiguredMesh> out = new ArrayList<>();
ResultConsumer resultConsumer = (renderType, shaded, data) -> { ResultConsumer resultConsumer = (renderType, shaded, data) -> {
Material material = materialFunc.apply(renderType, shaded); Material material = materialFunc.apply(renderType, shaded);
@ -77,16 +86,19 @@ public class ItemModelBuilder {
VertexView vertexView = new NoOverlayVertexView(); VertexView vertexView = new NoOverlayVertexView();
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView); MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
var mesh = new SimpleMesh(vertexView, meshData, "source=ItemModelBuilder," + "itemStack=" + itemStack + ",renderType=" + renderType + ",shaded=" + shaded); var mesh = new SimpleMesh(vertexView, meshData, "source=ItemModelBuilder," + "itemStack=" + itemStack + ",renderType=" + renderType + ",shaded=" + shaded);
out.add(new Model.ConfiguredMesh(material, mesh)); if (mesh.vertexCount() == 0) {
mesh.delete();
return;
} else {
out.add(new Model.ConfiguredMesh(material, mesh));
}
} }
}; };
var model = Minecraft.getInstance()
.getItemRenderer()
.getModel(itemStack, null, null, seed);
BakedModelBufferer.bufferItem(model, itemStack, displayContext, leftHand, poseStack, resultConsumer); BakedModelBufferer.bufferItem(model, itemStack, displayContext, leftHand, poseStack, resultConsumer);
return new SimpleModel(out.build()); out.sort(GLINT_LAST);
return new SimpleModel(ImmutableList.copyOf(out));
} }
} }

View file

@ -19,6 +19,9 @@ import com.mojang.math.Axis;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.MultiPartBakedModel;
import net.minecraft.client.resources.model.SimpleBakedModel;
import net.minecraft.client.resources.model.WeightedBakedModel;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource; import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.item.ItemEntity;
@ -32,23 +35,56 @@ public class ItemVisual extends SimpleEntityVisual<ItemEntity> {
private static final ThreadLocal<RandomSource> RANDOM = ThreadLocal.withInitial(RandomSource::createNewThreadLocalInstance); private static final ThreadLocal<RandomSource> RANDOM = ThreadLocal.withInitial(RandomSource::createNewThreadLocalInstance);
public static final ModelCache<ItemKey> MODEL_CACHE = new ModelCache<>(stack -> { public static final ModelCache<ItemKey> MODEL_CACHE = new ModelCache<>(stack -> {
return new ItemModelBuilder(stack.stack()).build(); return new ItemModelBuilder(stack.stack(), stack.model()).build();
}); });
private final PoseStack pPoseStack = new PoseStack(); private final PoseStack pPoseStack = new PoseStack();
private final BakedModel model; private final BakedModel model;
private final boolean isSupported;
private final InstanceRecycler<TransformedInstance> instances; private final InstanceRecycler<TransformedInstance> instances;
public ItemVisual(VisualizationContext ctx, ItemEntity entity) { public ItemVisual(VisualizationContext ctx, ItemEntity entity) {
super(ctx, entity); super(ctx, entity);
var item = entity.getItem();
model = Minecraft.getInstance() model = Minecraft.getInstance()
.getItemRenderer()
.getModel(item, entity.level(), null, entity.getId());
isSupported = isSupported(model);
var key = new ItemKey(item.copy(), model);
instances = new InstanceRecycler<>(() -> instancerProvider.instancer(InstanceTypes.TRANSFORMED, MODEL_CACHE.get(key))
.createInstance());
}
public static boolean isSupported(ItemEntity entity) {
var model = Minecraft.getInstance()
.getItemRenderer() .getItemRenderer()
.getModel(entity.getItem(), entity.level(), null, entity.getId()); .getModel(entity.getItem(), entity.level(), null, entity.getId());
instances = new InstanceRecycler<>(() -> instancerProvider.instancer(InstanceTypes.TRANSFORMED, MODEL_CACHE.get(new ItemKey(entity.getItem()))) return isSupported(model);
.createInstance()); }
public static boolean isSupported(BakedModel model) {
if (model.isCustomRenderer()) {
return false;
}
if (!model.getOverrides()
.getOverrides()
.isEmpty()) {
return false;
}
Class<? extends BakedModel> c = model.getClass();
if (!(c == SimpleBakedModel.class || c == MultiPartBakedModel.class || c == WeightedBakedModel.class)) {
return false;
}
return true;
} }
@Override @Override
@ -63,7 +99,7 @@ public class ItemVisual extends SimpleEntityVisual<ItemEntity> {
@Override @Override
public void beginFrame(Context ctx) { public void beginFrame(Context ctx) {
if (!isVisible(ctx.frustum())) { if (!isSupported || !isVisible(ctx.frustum())) {
return; return;
} }
@ -162,11 +198,7 @@ public class ItemVisual extends SimpleEntityVisual<ItemEntity> {
instances.delete(); instances.delete();
} }
public record ItemKey(ItemStack stack) { public record ItemKey(ItemStack stack, BakedModel model) {
public ItemKey(ItemStack stack) {
this.stack = stack.copy();
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
@ -177,20 +209,14 @@ public class ItemVisual extends SimpleEntityVisual<ItemEntity> {
return false; return false;
} }
ItemKey itemKey = (ItemKey) o; var o1 = (ItemKey) o;
if (stack.isEmpty()) { return Objects.equals(model, o1.model) && stack.hasFoil() == o1.stack.hasFoil();
return itemKey.stack.isEmpty();
} else {
return !itemKey.stack.isEmpty() && stack.getItem() == itemKey.stack.getItem() && Objects.equals(stack.getTag(), itemKey.stack.getTag());
}
} }
@Override @Override
public int hashCode() { public int hashCode() {
int out = stack.getItem() int out = model.hashCode();
.hashCode(); out = 31 * out + Boolean.hashCode(stack.hasFoil());
out = 31 * out + (stack.getTag() != null ? stack.getTag()
.hashCode() : 0);
return out; return out;
} }
} }

View file

@ -79,6 +79,7 @@ public class VanillaVisuals {
.apply(); .apply();
builder(EntityType.ITEM).factory(ItemVisual::new) builder(EntityType.ITEM).factory(ItemVisual::new)
.skipVanillaRender(ItemVisual::isSupported)
.apply(); .apply();
} }
} }