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.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.backend.Engine;
@ -32,7 +34,7 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
* <br>
* 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")
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.
if (checkAndWarnEmptyModel(key.model())) {
// 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));
}
return out;

View file

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

View file

@ -1,8 +1,6 @@
package com.jozufozu.flywheel.lib.model.baked;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
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.VertexConsumer;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceArraySet;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.MultiBufferSource;
@ -150,10 +150,9 @@ final class BakedModelBufferer {
var emitterSource = objects.emitterSource;
emitterSource.resultConsumer(consumer);
var itemRenderer = Minecraft.getInstance()
.getItemRenderer();
itemRenderer.render(stack, displayContext, leftHand, poseStack, emitterSource, 0, OverlayTexture.NO_OVERLAY, model);
Minecraft.getInstance()
.getItemRenderer()
.render(stack, displayContext, leftHand, poseStack, emitterSource, 0, OverlayTexture.NO_OVERLAY, model);
emitterSource.end();
}
@ -182,11 +181,8 @@ final class BakedModelBufferer {
}
private static class MeshEmitterSource implements MultiBufferSource {
private final Map<RenderType, MeshEmitter> emitters = new HashMap<>();
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<>();
private final Map<RenderType, MeshEmitter> emitters = new Reference2ReferenceOpenHashMap<>();
private final Set<MeshEmitter> active = new ReferenceArraySet<>();
@Nullable
private ResultConsumer resultConsumer;
@ -195,13 +191,6 @@ final class BakedModelBufferer {
public VertexConsumer getBuffer(RenderType renderType) {
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)) {
out.begin(resultConsumer);
}
@ -214,12 +203,7 @@ final class BakedModelBufferer {
emitter.end();
}
for (var emitter : activeGlint) {
emitter.end();
}
active.clear();
activeGlint.clear();
resultConsumer = null;
}

View file

@ -1,11 +1,14 @@
package com.jozufozu.flywheel.lib.model.baked;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
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.vertex.VertexView;
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.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
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 BakedModel model;
@Nullable
private PoseStack poseStack;
@Nullable
private ItemDisplayContext displayContext;
private boolean leftHand;
private int seed = 42;
@Nullable
private BiFunction<RenderType, Boolean, Material> materialFunc;
public ItemModelBuilder(ItemStack itemStack) {
public ItemModelBuilder(ItemStack itemStack, BakedModel model) {
this.itemStack = itemStack;
this.model = model;
}
public ItemModelBuilder poseStack(PoseStack poseStack) {
@ -51,11 +65,6 @@ public class ItemModelBuilder {
return this;
}
public ItemModelBuilder seed(int seed) {
this.seed = seed;
return this;
}
public ItemModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
this.materialFunc = materialFunc;
return this;
@ -69,7 +78,7 @@ public class ItemModelBuilder {
materialFunc = ModelUtil::getItemMaterial;
}
var out = ImmutableList.<Model.ConfiguredMesh>builder();
ArrayList<Model.ConfiguredMesh> out = new ArrayList<>();
ResultConsumer resultConsumer = (renderType, shaded, data) -> {
Material material = materialFunc.apply(renderType, shaded);
@ -77,16 +86,19 @@ public class ItemModelBuilder {
VertexView vertexView = new NoOverlayVertexView();
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
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);
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.renderer.LightTexture;
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.RandomSource;
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);
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 BakedModel model;
private final boolean isSupported;
private final InstanceRecycler<TransformedInstance> instances;
public ItemVisual(VisualizationContext ctx, ItemEntity entity) {
super(ctx, entity);
var item = entity.getItem();
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()
.getModel(entity.getItem(), entity.level(), null, entity.getId());
instances = new InstanceRecycler<>(() -> instancerProvider.instancer(InstanceTypes.TRANSFORMED, MODEL_CACHE.get(new ItemKey(entity.getItem())))
.createInstance());
return isSupported(model);
}
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
@ -63,7 +99,7 @@ public class ItemVisual extends SimpleEntityVisual<ItemEntity> {
@Override
public void beginFrame(Context ctx) {
if (!isVisible(ctx.frustum())) {
if (!isSupported || !isVisible(ctx.frustum())) {
return;
}
@ -162,11 +198,7 @@ public class ItemVisual extends SimpleEntityVisual<ItemEntity> {
instances.delete();
}
public record ItemKey(ItemStack stack) {
public ItemKey(ItemStack stack) {
this.stack = stack.copy();
}
public record ItemKey(ItemStack stack, BakedModel model) {
@Override
public boolean equals(Object o) {
@ -177,20 +209,14 @@ public class ItemVisual extends SimpleEntityVisual<ItemEntity> {
return false;
}
ItemKey itemKey = (ItemKey) o;
if (stack.isEmpty()) {
return itemKey.stack.isEmpty();
} else {
return !itemKey.stack.isEmpty() && stack.getItem() == itemKey.stack.getItem() && Objects.equals(stack.getTag(), itemKey.stack.getTag());
}
var o1 = (ItemKey) o;
return Objects.equals(model, o1.model) && stack.hasFoil() == o1.stack.hasFoil();
}
@Override
public int hashCode() {
int out = stack.getItem()
.hashCode();
out = 31 * out + (stack.getTag() != null ? stack.getTag()
.hashCode() : 0);
int out = model.hashCode();
out = 31 * out + Boolean.hashCode(stack.hasFoil());
return out;
}
}

View file

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