Model changes

- Buffered models directly consume IModels
 - Document IModel more
 - Move contraption world render spoofing to flywheel
 - Miscellaneous new RenderMaths
 - Added WorldModel, renders many blocks given a world instance
 - Fix broken transparency on contraptions when using Flywheel
This commit is contained in:
Jozufozu 2021-07-29 01:37:47 -07:00
parent 17d5081345
commit d69ff7054e
14 changed files with 311 additions and 78 deletions

View file

@ -97,6 +97,14 @@ public class VecBuffer {
return this;
}
public VecBuffer putColor(byte r, byte g, byte b, byte a) {
internal.put(r);
internal.put(g);
internal.put(b);
internal.put(a);
return this;
}
public VecBuffer putVec3(float x, float y, float z) {
internal.putFloat(x);
internal.putFloat(y);

View file

@ -11,9 +11,9 @@ import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.backend.material.MaterialSpec;
import com.jozufozu.flywheel.backend.model.BufferedModel;
import com.jozufozu.flywheel.backend.model.IBufferedModel;
import com.jozufozu.flywheel.backend.model.IndexedModel;
import com.jozufozu.flywheel.core.model.IModel;
import com.jozufozu.flywheel.core.model.ModelUtil;
import com.jozufozu.flywheel.util.AttribUtil;
/**
@ -37,7 +37,7 @@ import com.jozufozu.flywheel.util.AttribUtil;
public class Instancer<D extends InstanceData> {
protected final Supplier<IModel> gen;
protected BufferedModel model;
protected IBufferedModel model;
protected final VertexFormat instanceFormat;
protected final IInstanceFactory<D> factory;
@ -92,7 +92,7 @@ public class Instancer<D extends InstanceData> {
}
private void init() {
model = ModelUtil.getIndexedModel(gen.get());
model = new IndexedModel(gen.get());
initialized = true;
if (model.getVertexCount() <= 0)

View file

@ -1,23 +1,23 @@
package com.jozufozu.flywheel.backend.model;
import java.util.function.Supplier;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.core.model.IModel;
import com.jozufozu.flywheel.util.AttribUtil;
public class ArrayModelRenderer extends ModelRenderer {
protected GlVertexArray vao;
public ArrayModelRenderer(BufferedModel model) {
public ArrayModelRenderer(Supplier<IModel> model) {
super(model);
vao = new GlVertexArray();
vao.bind();
model.setupState();
vao.unbind();
model.clearState();
}
@Override
public void draw() {
if (!model.valid()) return;
if (!isInitialized()) init();
if (!isValid()) return;
vao.bind();
@ -25,4 +25,31 @@ public class ArrayModelRenderer extends ModelRenderer {
vao.unbind();
}
private boolean isValid() {
return model != null && model.valid();
}
@Override
protected void init() {
initialized = true;
IModel model = modelSupplier.get();
if (model.vertexCount() <= 0) return;
this.model = new IndexedModel(model);
vao = new GlVertexArray();
vao.bind();
// bind the model's vbo to our vao
this.model.setupState();
AttribUtil.enableArrays(this.model.getAttributeCount());
vao.unbind();
this.model.clearState();
}
}

View file

@ -9,46 +9,45 @@ import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.core.model.IModel;
import com.jozufozu.flywheel.util.AttribUtil;
public class BufferedModel {
public class BufferedModel implements IBufferedModel {
protected final IModel model;
protected final GlPrimitive primitiveMode;
protected final ByteBuffer data;
protected final VertexFormat format;
protected final int vertexCount;
protected GlBuffer vbo;
protected boolean deleted;
public BufferedModel(GlPrimitive primitiveMode, VertexFormat format, ByteBuffer data, int vertices) {
public BufferedModel(GlPrimitive primitiveMode, IModel model) {
this.model = model;
this.primitiveMode = primitiveMode;
this.data = data;
this.format = format;
this.vertexCount = vertices;
vbo = new GlBuffer(GlBufferType.ARRAY_BUFFER);
vbo.bind();
// allocate the buffer on the gpu
vbo.alloc(this.data.capacity());
vbo.alloc(model.size());
// mirror it in system memory so we can write to it, and upload our model.
vbo.getBuffer(0, this.data.capacity())
.put(this.data)
.flush();
MappedBuffer buffer = vbo.getBuffer(0, model.size());
model.buffer(buffer);
buffer.flush();
vbo.unbind();
}
public VertexFormat getFormat() {
return format;
return model.format();
}
public int getVertexCount() {
return vertexCount;
return model.vertexCount();
}
public boolean valid() {
return vertexCount > 0 && !deleted;
return getVertexCount() > 0 && !deleted;
}
/**
@ -57,7 +56,7 @@ public class BufferedModel {
public void setupState() {
vbo.bind();
AttribUtil.enableArrays(getAttributeCount());
format.vertexAttribPointers(0);
getFormat().vertexAttribPointers(0);
}
public void clearState() {
@ -66,7 +65,7 @@ public class BufferedModel {
}
public void drawCall() {
glDrawArrays(primitiveMode.glEnum, 0, vertexCount);
glDrawArrays(primitiveMode.glEnum, 0, getVertexCount());
}
/**
@ -75,7 +74,7 @@ public class BufferedModel {
public void drawInstances(int instanceCount) {
if (!valid()) return;
Backend.getInstance().compat.drawInstanced.drawArraysInstanced(primitiveMode, 0, vertexCount, instanceCount);
Backend.getInstance().compat.drawInstanced.drawArraysInstanced(primitiveMode, 0, getVertexCount(), instanceCount);
}
public void delete() {
@ -84,10 +83,5 @@ public class BufferedModel {
deleted = true;
vbo.delete();
}
public int getAttributeCount() {
return format.getAttributeCount();
}
}

View file

@ -0,0 +1,32 @@
package com.jozufozu.flywheel.backend.model;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
public interface IBufferedModel {
VertexFormat getFormat();
int getVertexCount();
boolean valid();
/**
* The VBO/VAO should be bound externally.
*/
void setupState();
void clearState();
void drawCall();
/**
* Draws many instances of this model, assuming the appropriate state is already bound.
*/
void drawInstances(int instanceCount);
void delete();
default int getAttributeCount() {
return getFormat().getAttributeCount();
}
}

View file

@ -8,6 +8,7 @@ import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.core.QuadConverter;
import com.jozufozu.flywheel.core.model.IModel;
/**
* An indexed triangle model. Just what the driver ordered.
@ -18,15 +19,10 @@ public class IndexedModel extends BufferedModel {
protected ElementBuffer ebo;
public IndexedModel(VertexFormat modelFormat, ByteBuffer buf, int vertices, ElementBuffer ebo) {
super(GlPrimitive.TRIANGLES, modelFormat, buf, vertices);
public IndexedModel(IModel model) {
super(GlPrimitive.TRIANGLES, model);
this.ebo = ebo;
}
public static IndexedModel fromSequentialQuads(VertexFormat modelFormat, ByteBuffer quads, int vertices) {
return new IndexedModel(modelFormat, quads, vertices, QuadConverter.getInstance()
.quads2Tris(vertices / 4));
this.ebo = model.createEBO();
}
@Override
@ -48,13 +44,8 @@ public class IndexedModel extends BufferedModel {
@Override
public void drawInstances(int instanceCount) {
if (vertexCount <= 0 || deleted) return;
if (!valid()) return;
Backend.getInstance().compat.drawInstanced.drawElementsInstanced(primitiveMode, ebo.elementCount, ebo.eboIndexType, 0, instanceCount);
}
@Override
public void delete() {
super.delete();
}
}

View file

@ -1,17 +1,26 @@
package com.jozufozu.flywheel.backend.model;
import java.util.function.Supplier;
import com.jozufozu.flywheel.core.model.IModel;
public class ModelRenderer {
protected BufferedModel model;
protected Supplier<IModel> modelSupplier;
protected IBufferedModel model;
public ModelRenderer(BufferedModel model) {
this.model = model;
protected boolean initialized;
public ModelRenderer(Supplier<IModel> modelSupplier) {
this.modelSupplier = modelSupplier;
}
/**
* Renders this model, checking first if there is anything to render.
*/
public void draw() {
if (!isInitialized()) init();
if (!model.valid()) return;
model.setupState();
@ -19,7 +28,21 @@ public class ModelRenderer {
model.clearState();
}
protected void init() {
initialized = true;
IModel model = modelSupplier.get();
if (model.vertexCount() <= 0) return;
this.model = new IndexedModel(model);
}
public boolean isInitialized() {
return initialized;
}
public void delete() {
if (model != null)
model.delete();
}
}

View file

@ -9,8 +9,17 @@ public class Formats {
.addAttributes(CommonAttributes.VEC3, CommonAttributes.NORMAL, CommonAttributes.UV)
.build();
public static final VertexFormat COLORED_LIT_MODEL = VertexFormat.builder()
.addAttributes(CommonAttributes.VEC3,
CommonAttributes.NORMAL,
CommonAttributes.UV,
CommonAttributes.RGBA,
CommonAttributes.LIGHT)
.build();
public static final VertexFormat TRANSFORMED = litInstance().addAttributes(MatrixAttributes.MAT4, MatrixAttributes.MAT3)
.build();
public static final VertexFormat ORIENTED = litInstance().addAttributes(CommonAttributes.VEC3, CommonAttributes.VEC3, CommonAttributes.QUATERNION)
.build();

View file

@ -23,6 +23,9 @@ import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* A model of a single block.
*/
public class BlockModel implements IModel {
private static final MatrixStack IDENTITY = new MatrixStack();
@ -69,12 +72,6 @@ public class BlockModel implements IModel {
}
}
@Override
public ElementBuffer createEBO() {
return QuadConverter.getInstance()
.quads2Tris(vertexCount() / 4);
}
public static BufferBuilder getBufferBuilder(IBakedModel model, BlockState referenceState, MatrixStack ms) {
Minecraft mc = Minecraft.getInstance();
BlockRendererDispatcher dispatcher = mc.getBlockRenderer();

View file

@ -3,9 +3,27 @@ package com.jozufozu.flywheel.core.model;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.gl.buffer.VecBuffer;
import com.jozufozu.flywheel.backend.model.ElementBuffer;
import com.jozufozu.flywheel.core.QuadConverter;
/**
* A model that can be rendered by flywheel.
*
* <p>
* It is expected that the following assertion will not fail:
* </p>
*
* <pre>{@code
* IModel model = ...;
* VecBuffer into = ...;
*
* int initial = VecBuffer.unwrap().position();
*
* model.buffer(into);
*
* int final = VecBuffer.unwrap().position();
*
* assert model.size() == final - initial;
* }</pre>
*/
public interface IModel {
@ -14,12 +32,34 @@ public interface IModel {
*/
void buffer(VecBuffer buffer);
/**
* @return The number of vertices the model has.
*/
int vertexCount();
/**
* @return The format of this model's vertices
*/
VertexFormat format();
ElementBuffer createEBO();
/**
* Create an element buffer object that indexes the vertices of this model.
*
* <p>
* Very often models in minecraft are made up of sequential quads, which is a very predictable pattern.
* The default implementation accommodates this, however this can be overridden to change the behavior and
* support more complex models.
* </p>
* @return an element buffer object indexing this model's vertices.
*/
default ElementBuffer createEBO() {
return QuadConverter.getInstance()
.quads2Tris(vertexCount() / 4);
}
/**
* The size in bytes that this model's data takes up.
*/
default int size() {
return vertexCount() * format().getStride();
}

View file

@ -43,10 +43,4 @@ public class ModelPart implements IModel {
public VertexFormat format() {
return Formats.UNLIT_MODEL;
}
@Override
public ElementBuffer createEBO() {
return QuadConverter.getInstance()
.quads2Tris(vertices / 4);
}
}

View file

@ -1,21 +1,62 @@
package com.jozufozu.flywheel.core.model;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.lwjgl.opengl.GL11.GL_QUADS;
import com.jozufozu.flywheel.backend.gl.buffer.VecBuffer;
import com.jozufozu.flywheel.backend.model.IndexedModel;
import java.util.Collection;
import java.util.Random;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.block.BlockRenderType;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockModelRenderer;
import net.minecraft.client.renderer.BlockModelShapes;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeLookup;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraft.world.gen.feature.template.Template;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.model.data.EmptyModelData;
import net.minecraftforge.common.util.Lazy;
public class ModelUtil {
public static IndexedModel getIndexedModel(IModel blockModel) {
ByteBuffer vertices = ByteBuffer.allocate(blockModel.size());
vertices.order(ByteOrder.nativeOrder());
private static final Lazy<BlockModelRenderer> MODEL_RENDERER = Lazy.of(() -> new BlockModelRenderer(Minecraft.getInstance().getBlockColors()));
private static final Lazy<BlockModelShapes> BLOCK_MODELS = Lazy.of(() -> Minecraft.getInstance().getModelManager().getBlockModelShaper());
blockModel.buffer(new VecBuffer(vertices));
public static BufferBuilder getBufferBuilderFromTemplate(IBlockDisplayReader renderWorld, RenderType layer, Collection<Template.BlockInfo> blocks) {
MatrixStack ms = new MatrixStack();
Random random = new Random();
BufferBuilder builder = new BufferBuilder(DefaultVertexFormats.BLOCK.getIntegerSize());
builder.begin(GL_QUADS, DefaultVertexFormats.BLOCK);
((Buffer) vertices).rewind();
ForgeHooksClient.setRenderLayer(layer);
BlockModelRenderer.enableCaching();
for (Template.BlockInfo info : blocks) {
BlockState state = info.state;
return new IndexedModel(blockModel.format(), vertices, blockModel.vertexCount(), blockModel.createEBO());
if (state.getRenderShape() != BlockRenderType.MODEL)
continue;
if (!RenderTypeLookup.canRenderInLayer(state, layer))
continue;
BlockPos pos = info.pos;
ms.pushPose();
ms.translate(pos.getX(), pos.getY(), pos.getZ());
MODEL_RENDERER.get().renderModel(renderWorld, BLOCK_MODELS.get().getBlockModel(state), state, pos, ms, builder, true,
random, 42, OverlayTexture.NO_OVERLAY, EmptyModelData.INSTANCE);
ms.popPose();
}
BlockModelRenderer.clearCache();
ForgeHooksClient.setRenderLayer(null);
builder.end();
return builder;
}
}

View file

@ -0,0 +1,53 @@
package com.jozufozu.flywheel.core.model;
import java.util.Collection;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.gl.buffer.VecBuffer;
import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.util.BufferBuilderReader;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraft.world.gen.feature.template.Template;
public class WorldModel implements IModel {
private final BufferBuilderReader reader;
public WorldModel(IBlockDisplayReader renderWorld, RenderType layer, Collection<Template.BlockInfo> blocks) {
reader = new BufferBuilderReader(ModelUtil.getBufferBuilderFromTemplate(renderWorld, layer, blocks));
}
@Override
public void buffer(VecBuffer vertices) {
for (int i = 0; i < vertexCount(); i++) {
vertices.putVec3(reader.getX(i), reader.getY(i), reader.getZ(i));
vertices.putVec3(reader.getNX(i), reader.getNY(i), reader.getNZ(i));
vertices.putVec2(reader.getU(i), reader.getV(i));
vertices.putColor(reader.getR(i), reader.getG(i), reader.getB(i), reader.getA(i));
int light = reader.getLight(i);
byte block = (byte) (LightTexture.block(light) << 4);
byte sky = (byte) (LightTexture.sky(light) << 4);
vertices.putVec2(block, sky);
}
}
@Override
public int vertexCount() {
return reader.getVertexCount();
}
@Override
public VertexFormat format() {
return Formats.COLORED_LIT_MODEL;
}
}

View file

@ -2,7 +2,31 @@ package com.jozufozu.flywheel.util;
public class RenderMath {
/**
* Convert a signed, normalized floating point value into a normalized byte.
*/
public static byte nb(float f) {
return (byte) (f * 127);
}
/**
* Convert a signed byte into a normalized float.
*/
public static float f(byte b) {
return b / 127f;
}
/**
* Convert an unsigned byte into a normalized float.
*/
public static float uf(byte b) {
return (float) (Byte.toUnsignedInt(b)) / 255f;
}
/**
* Convert an unsigned, normalized float into an unsigned normalized byte.
*/
public static byte unb(float f) {
return (byte) Math.floor(f * 255);
}
}