Instanced Shulker boxes

- Preliminary concept of RenderStates
 - Vanilla has this too but it's bloated
This commit is contained in:
Jozufozu 2021-07-18 15:05:12 -07:00
parent 04b906d06f
commit 0d474d0d04
13 changed files with 446 additions and 70 deletions

View file

@ -0,0 +1,55 @@
package com.jozufozu.flywheel.backend.gl;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.glActiveTexture;
import com.mojang.blaze3d.platform.GlStateManager;
public enum GlTextureUnit {
T0(0),
T1(1),
T2(2),
T3(3),
T4(4),
T5(5),
T6(6),
T7(7),
T8(8),
T9(9),
T10(10),
T11(11),
T12(12),
T13(13),
T14(14),
T15(15),
T16(16),
T17(17),
T18(18),
T19(19),
T20(20),
T21(21),
T22(22),
T23(23),
T24(24),
T25(25),
T26(26),
T27(27),
T28(28),
T29(29),
T30(30),
T31(31),
;
public final int number;
public final int glEnum;
GlTextureUnit(int unit) {
this.number = unit;
this.glEnum = GL_TEXTURE0 + unit;
}
public void makeActive() {
GlStateManager._activeTexture(glEnum);
}
}

View file

@ -4,7 +4,8 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.state.IRenderState;
import com.jozufozu.flywheel.backend.state.TextureRenderState;
import com.jozufozu.flywheel.core.Materials;
import com.jozufozu.flywheel.core.WorldContext;
import com.jozufozu.flywheel.core.materials.ModelData;
@ -13,7 +14,6 @@ import com.jozufozu.flywheel.core.shader.IProgramCallback;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.util.WeakHashSet;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.inventory.container.PlayerContainer;
@ -30,11 +30,8 @@ public class MaterialManager<P extends WorldProgram> {
protected final WorldContext<P> context;
protected final Map<MaterialSpec<?>, InstanceMaterial<?>> atlasMaterials;
protected final ArrayList<MaterialRenderer<P>> atlasRenderers;
protected final Map<ResourceLocation, ArrayList<MaterialRenderer<P>>> renderers;
protected final Map<ResourceLocation, Map<MaterialSpec<?>, InstanceMaterial<?>>> materials;
protected final Map<IRenderState, ArrayList<MaterialRenderer<P>>> renderers;
protected final Map<IRenderState, Map<MaterialSpec<?>, InstanceMaterial<?>>> materials;
protected BlockPos originCoordinate = BlockPos.ZERO;
@ -43,11 +40,6 @@ public class MaterialManager<P extends WorldProgram> {
public MaterialManager(WorldContext<P> context) {
this.context = context;
this.atlasMaterials = new HashMap<>();
this.atlasRenderers = new ArrayList<>(Backend.getInstance()
.allMaterials()
.size());
this.materials = new HashMap<>();
this.renderers = new HashMap<>();
@ -80,52 +72,43 @@ public class MaterialManager<P extends WorldProgram> {
translate.multiplyBackward(viewProjection);
for (MaterialRenderer<P> material : atlasRenderers) {
material.render(layer, translate, camX, camY, camZ, callback);
}
for (Map.Entry<ResourceLocation, ArrayList<MaterialRenderer<P>>> entry : renderers.entrySet()) {
Minecraft.getInstance().textureManager.bind(entry.getKey());
for (Map.Entry<IRenderState, ArrayList<MaterialRenderer<P>>> entry : renderers.entrySet()) {
entry.getKey().bind();
for (MaterialRenderer<P> materialRenderer : entry.getValue()) {
materialRenderer.render(layer, translate, camX, camY, camZ, callback);
}
entry.getKey().unbind();
}
}
public void delete() {
atlasMaterials.values()
.forEach(InstanceMaterial::delete);
materials.values()
.stream()
.flatMap(m -> m.values()
.stream())
.forEach(InstanceMaterial::delete);
atlasMaterials.clear();
atlasRenderers.clear();
materials.clear();
renderers.clear();
}
@SuppressWarnings("unchecked")
public <D extends InstanceData> InstanceMaterial<D> getMaterial(MaterialSpec<D> materialType) {
return (InstanceMaterial<D>) this.atlasMaterials.computeIfAbsent(materialType, type -> {
InstanceMaterial<?> material = new InstanceMaterial<>(this::getOriginCoordinate, type);
return getMaterial(materialType, PlayerContainer.BLOCK_ATLAS);
}
this.atlasRenderers.add(new MaterialRenderer<>(context.getProgramSupplier(type.getProgramName()), material));
return material;
});
public <D extends InstanceData> InstanceMaterial<D> getMaterial(MaterialSpec<D> materialType, ResourceLocation texture) {
return getMaterial(materialType, TextureRenderState.get(texture));
}
@SuppressWarnings("unchecked")
public <D extends InstanceData> InstanceMaterial<D> getMaterial(MaterialSpec<D> materialType, ResourceLocation texture) {
return (InstanceMaterial<D>) materials.computeIfAbsent(texture, $ -> new HashMap<>())
public <D extends InstanceData> InstanceMaterial<D> getMaterial(MaterialSpec<D> materialType, IRenderState state) {
return (InstanceMaterial<D>) materials.computeIfAbsent(state, $ -> new HashMap<>())
.computeIfAbsent(materialType, type -> {
InstanceMaterial<?> material = new InstanceMaterial<>(this::getOriginCoordinate, type);
this.renderers.computeIfAbsent(texture, $ -> new ArrayList<>())
this.renderers.computeIfAbsent(state, $ -> new ArrayList<>())
.add(new MaterialRenderer<>(context.getProgramSupplier(type.getProgramName()), material));
return material;
@ -166,8 +149,7 @@ public class MaterialManager<P extends WorldProgram> {
.flatMap(m -> m.values()
.stream())
.forEach(InstanceMaterial::clear);
atlasMaterials.values()
.forEach(InstanceMaterial::clear);
listeners.forEach(OriginShiftListener::onOriginShift);
}
}

View file

@ -0,0 +1,19 @@
package com.jozufozu.flywheel.backend.state;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import net.minecraft.util.ResourceLocation;
public interface IRenderState {
void bind();
void unbind();
@Nullable
default ResourceLocation getTexture(GlTextureUnit textureUnit) {
return null;
}
}

View file

@ -0,0 +1,20 @@
package com.jozufozu.flywheel.backend.state;
import com.mojang.blaze3d.platform.GlStateManager;
public class NoCullRenderState implements IRenderState {
public static final NoCullRenderState INSTANCE = new NoCullRenderState();
protected NoCullRenderState() { }
@Override
public void bind() {
GlStateManager._disableCull();
}
@Override
public void unbind() {
GlStateManager._enableCull();
}
}

View file

@ -0,0 +1,81 @@
package com.jozufozu.flywheel.backend.state;
import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import net.minecraft.util.ResourceLocation;
public class RenderState implements IRenderState {
private final Map<GlTextureUnit, ResourceLocation> textures;
private final ImmutableList<IRenderState> states;
public RenderState(Map<GlTextureUnit, ResourceLocation> textures, ImmutableList<IRenderState> states) {
this.textures = textures;
this.states = states;
}
@Override
public void bind() {
states.forEach(IRenderState::bind);
}
@Override
public void unbind() {
states.forEach(IRenderState::unbind);
}
@Nullable
@Override
public ResourceLocation getTexture(GlTextureUnit textureUnit) {
return textures.get(textureUnit);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RenderState that = (RenderState) o;
return states.equals(that.states);
}
@Override
public int hashCode() {
return Objects.hash(states);
}
public static StateBuilder builder() {
return new StateBuilder();
}
public static class StateBuilder {
private final ImmutableList.Builder<IRenderState> states = ImmutableList.builder();
private final Map<GlTextureUnit, ResourceLocation> textures = new EnumMap<>(GlTextureUnit.class);
public StateBuilder texture(ResourceLocation name) {
return addState(TextureRenderState.get(name));
}
public StateBuilder addState(IRenderState state) {
if (state instanceof TextureRenderState) {
TextureRenderState tex = (TextureRenderState) state;
if (textures.put(tex.unit, tex.location) == null) {
states.add(state);
}
} else {
states.add(state);
}
return this;
}
public RenderState build() {
return new RenderState(textures, states.build());
}
}
}

View file

@ -0,0 +1,64 @@
package com.jozufozu.flywheel.backend.state;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.util.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
public class TextureRenderState implements IRenderState {
private static final Map<Pair<GlTextureUnit, ResourceLocation>, TextureRenderState> states = new HashMap<>();
public final GlTextureUnit unit;
public final ResourceLocation location;
private TextureRenderState(GlTextureUnit unit, ResourceLocation location) {
this.unit = unit;
this.location = location;
}
public static TextureRenderState get(ResourceLocation texture) {
return get(GlTextureUnit.T0, texture);
}
public static TextureRenderState get(GlTextureUnit unit, ResourceLocation texture) {
return states.computeIfAbsent(Pair.of(unit, texture), p -> new TextureRenderState(p.getFirst(), p.getSecond()));
}
@Override
public void bind() {
unit.makeActive();
Minecraft.getInstance().textureManager.bind(location);
}
@Override
public void unbind() {
}
@Nullable
@Override
public ResourceLocation getTexture(GlTextureUnit textureUnit) {
if (textureUnit == unit) return location;
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TextureRenderState that = (TextureRenderState) o;
return location.equals(that.location);
}
@Override
public int hashCode() {
return Objects.hash(location);
}
}

View file

@ -1,24 +1,18 @@
package com.jozufozu.flywheel.core.crumbling;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.glActiveTexture;
import java.util.ArrayList;
import java.util.Map;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.backend.instancing.MaterialManager;
import com.jozufozu.flywheel.backend.instancing.MaterialRenderer;
import com.jozufozu.flywheel.backend.state.IRenderState;
import com.jozufozu.flywheel.core.WorldContext;
import com.jozufozu.flywheel.core.atlas.AtlasInfo;
import com.jozufozu.flywheel.core.atlas.SheetData;
import com.jozufozu.flywheel.core.shader.IProgramCallback;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.inventory.container.PlayerContainer;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.vector.Matrix4f;
@ -44,23 +38,29 @@ public class CrumblingMaterialManager extends MaterialManager<CrumblingProgram>
translate.multiplyBackward(viewProjection);
TextureManager textureManager = Minecraft.getInstance().textureManager;
for (Map.Entry<IRenderState, ArrayList<MaterialRenderer<CrumblingProgram>>> entry : renderers.entrySet()) {
IRenderState key = entry.getKey();
key.bind();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureManager.getTexture(PlayerContainer.BLOCK_ATLAS)
.getId());
int width;
int height;
for (MaterialRenderer<CrumblingProgram> material : atlasRenderers) {
material.render(layer, translate, camX, camY, camZ, CrumblingProgram::setDefaultAtlasSize);
}
ResourceLocation texture = key.getTexture(GlTextureUnit.T0);
for (Map.Entry<ResourceLocation, ArrayList<MaterialRenderer<CrumblingProgram>>> entry : renderers.entrySet()) {
glBindTexture(GL_TEXTURE_2D, textureManager.getTexture(entry.getKey())
.getId());
SheetData atlasData = AtlasInfo.getAtlasData(entry.getKey());
for (MaterialRenderer<CrumblingProgram> materialRenderer : entry.getValue()) {
materialRenderer.render(layer, translate, camX, camY, camZ, p -> p.setAtlasSize(atlasData.width, atlasData.height));
if (texture != null) {
SheetData atlasData = AtlasInfo.getAtlasData(texture);
width = atlasData.width;
height = atlasData.height;
} else {
width = height = 256;
}
for (MaterialRenderer<CrumblingProgram> materialRenderer : entry.getValue()) {
materialRenderer.render(layer, translate, camX, camY, camZ, p -> p.setAtlasSize(width, height));
}
key.unbind();
}
}
}

View file

@ -35,16 +35,6 @@ public class CrumblingProgram extends WorldProgram {
glUniform2f(uTextureScale, x, y);
}
public void setDefaultAtlasSize() {
SheetData atlasData = AtlasInfo.getAtlasData(PlayerContainer.BLOCK_ATLAS);
if (atlasData == null) return;
int width = atlasData.width;
int height = atlasData.height;
setAtlasSize(width, height);
}
public void setAtlasSize(int width, int height) {
AtlasTexture blockAtlas = AtlasInfo.getAtlas(PlayerContainer.BLOCK_ATLAS);
if (blockAtlas == null) return;

View file

@ -127,8 +127,6 @@ public class PartBuilder {
/**
* Pulls the cuboid "inside out" through the Y and Z axes.
*
* See the {@link com.jozufozu.flywheel.vanilla.SignInstance sign} renderer for the use case.
*/
public CuboidBuilder invertYZ() {
this.invertYZ = true;

View file

@ -45,11 +45,27 @@ public interface TransformStack {
}
default TransformStack centre() {
return translate(CENTER);
return translateAll(0.5);
}
default TransformStack unCentre() {
return translateBack(CENTER);
return translateAll(-0.5);
}
default TransformStack translateAll(double v) {
return translate(v, v, v);
}
default TransformStack translateX(double x) {
return translate(x, 0, 0);
}
default TransformStack translateY(double y) {
return translate(0, y, 0);
}
default TransformStack translateZ(double z) {
return translate(0, 0, z);
}
default TransformStack translate(Vector3i vec) {

View file

@ -0,0 +1,18 @@
package com.jozufozu.flywheel.vanilla;
import java.util.List;
import java.util.stream.Collectors;
import com.jozufozu.flywheel.backend.state.IRenderState;
import com.jozufozu.flywheel.backend.state.NoCullRenderState;
import com.jozufozu.flywheel.backend.state.RenderState;
import net.minecraft.client.renderer.Atlases;
import net.minecraft.client.renderer.model.RenderMaterial;
public class RenderStates {
public static final IRenderState SHULKER = RenderState.builder()
.texture(Atlases.SHULKER_SHEET)
.addState(NoCullRenderState.INSTANCE)
.build();
}

View file

@ -0,0 +1,130 @@
package com.jozufozu.flywheel.vanilla;
import com.jozufozu.flywheel.backend.instancing.IDynamicInstance;
import com.jozufozu.flywheel.backend.instancing.MaterialManager;
import com.jozufozu.flywheel.backend.instancing.tile.TileEntityInstance;
import com.jozufozu.flywheel.backend.model.BufferedModel;
import com.jozufozu.flywheel.core.Materials;
import com.jozufozu.flywheel.core.materials.ModelData;
import com.jozufozu.flywheel.core.model.ModelPart;
import com.jozufozu.flywheel.util.AnimationTickHolder;
import com.jozufozu.flywheel.util.transform.MatrixTransformStack;
import net.minecraft.block.ShulkerBoxBlock;
import net.minecraft.client.renderer.Atlases;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.item.DyeColor;
import net.minecraft.tileentity.ShulkerBoxTileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.vector.Quaternion;
import net.minecraft.util.math.vector.Vector3f;
public class ShulkerBoxInstance extends TileEntityInstance<ShulkerBoxTileEntity> implements IDynamicInstance {
private final TextureAtlasSprite texture;
private final ModelData base;
private final ModelData lid;
private final MatrixTransformStack stack;
private float lastProgress = Float.NaN;
public ShulkerBoxInstance(MaterialManager<?> materialManager, ShulkerBoxTileEntity tile) {
super(materialManager, tile);
DyeColor color = tile.getColor();
if (color == null) {
texture = Atlases.DEFAULT_SHULKER_TEXTURE_LOCATION.sprite();
} else {
texture = Atlases.SHULKER_TEXTURE_LOCATION.get(color.getId()).sprite();
}
Quaternion rotation = getDirection().getRotation();
stack = new MatrixTransformStack();
stack.translate(getInstancePosition())
.scale(0.9995f)
.translateAll(0.00025)
.centre()
.multiply(rotation)
.unCentre();
base = makeBaseInstance().setTransform(stack.unwrap());
stack.translateY(0.25);
lid = makeLidInstance().setTransform(stack.unwrap());
}
@Override
public void beginFrame() {
float progress = tile.getProgress(AnimationTickHolder.getPartialTicks());
if (progress == lastProgress) return;
lastProgress = progress;
Quaternion spin = Vector3f.YP.rotationDegrees(270.0F * progress);
stack.push()
.centre()
.multiply(spin)
.unCentre()
.translateY(progress * 0.5f);
lid.setTransform(stack.unwrap());
stack.pop();
}
@Override
public void remove() {
base.delete();
lid.delete();
}
@Override
public void updateLight() {
relight(pos, base, lid);
}
private ModelData makeBaseInstance() {
return materialManager.getMaterial(Materials.TRANSFORMED, RenderStates.SHULKER)
.get("base_" + texture.getName(), this::makeBaseModel)
.createInstance();
}
private ModelData makeLidInstance() {
return materialManager.getMaterial(Materials.TRANSFORMED, RenderStates.SHULKER)
.get("lid_" + texture.getName(), this::makeLidModel)
.createInstance();
}
private BufferedModel makeBaseModel() {
return ModelPart.builder(64, 64)
.sprite(texture)
.cuboid()
.textureOffset(0, 28)
.size(16, 8, 16)
.invertYZ()
.endCuboid()
.build();
}
private BufferedModel makeLidModel() {
return ModelPart.builder(64, 64)
.sprite(texture)
.cuboid()
.size(16, 12, 16)
.invertYZ()
.endCuboid()
.build();
}
private Direction getDirection() {
if (blockState.getBlock() instanceof ShulkerBoxBlock) {
return blockState.getValue(ShulkerBoxBlock.FACING);
}
return Direction.UP;
}
}

View file

@ -7,7 +7,6 @@ import net.minecraft.tileentity.TileEntityType;
/**
* TODO:
* <table>
* <tr><td>{@link TileEntityType#SHULKER_BOX}</td><td> {@link net.minecraft.client.renderer.tileentity.ShulkerBoxTileEntityRenderer ShulkerBoxTileEntityRenderer}</td></tr>
* <tr><td>{@link TileEntityType#SIGN}</td><td> {@link net.minecraft.client.renderer.tileentity.SignTileEntityRenderer SignTileEntityRenderer}</td></tr>
* <tr><td>{@link TileEntityType#PISTON}</td><td> {@link net.minecraft.client.renderer.tileentity.PistonTileEntityRenderer PistonTileEntityRenderer}</td></tr>
* <tr><td>{@link TileEntityType#CONDUIT}</td><td> {@link net.minecraft.client.renderer.tileentity.ConduitTileEntityRenderer ConduitTileEntityRenderer}</td></tr>
@ -43,5 +42,9 @@ public class VanillaInstances {
r.tile(TileEntityType.BELL)
.setSkipRender(true)
.factory(BellInstance::new);
r.tile(TileEntityType.SHULKER_BOX)
.setSkipRender(true)
.factory(ShulkerBoxInstance::new);
}
}