mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-28 16:06:28 +01:00
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:
parent
163649e792
commit
b51ccd43e2
6 changed files with 86 additions and 60 deletions
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue