Make the BatchingEngine not jittery

- Stop having threads compete for a single BufferBuilder
 - ...by skirting around minecraft's BufferSource
 - Begin work on making vertex writing sane
This commit is contained in:
Jozufozu 2021-12-21 22:47:29 -08:00
parent 831f990153
commit b0f6d07b0b
32 changed files with 948 additions and 397 deletions

View file

@ -11,6 +11,7 @@ import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.instancing.batching.WaitGroup;
import net.minecraft.util.Mth;
@ -104,7 +105,7 @@ public class BatchExecutor implements TaskEngine {
Runnable job;
// Finish everyone else's work...
while ((job = getNextTask(false)) != null) {
while ((job = this.jobQueue.pollLast()) != null) {
processTask(job);
}
@ -116,10 +117,10 @@ public class BatchExecutor implements TaskEngine {
}
@Nullable
private Runnable getNextTask(boolean block) {
Runnable job = this.jobQueue.poll();
private Runnable getNextTask() {
Runnable job = this.jobQueue.pollFirst();
if (job == null && block) {
if (job == null) {
synchronized (BatchExecutor.this.jobNotifier) {
try {
BatchExecutor.this.jobNotifier.wait();
@ -134,6 +135,8 @@ public class BatchExecutor implements TaskEngine {
private void processTask(Runnable job) {
try {
job.run();
} catch (Exception e) {
Flywheel.log.error(e);
} finally {
BatchExecutor.this.wg.done();
}
@ -158,13 +161,13 @@ public class BatchExecutor implements TaskEngine {
public void run() {
// Run until the chunk builder shuts down
while (this.running.get()) {
Runnable job = BatchExecutor.this.getNextTask(true);
Runnable job = BatchExecutor.this.getNextTask();
if (job == null) {
continue;
}
processTask(job);
BatchExecutor.this.processTask(job);
}
}

View file

@ -116,7 +116,7 @@ public class InstanceWorld {
*/
public void renderLayer(RenderLayerEvent event) {
taskEngine.syncPoint();
engine.render(event, event.buffers.bufferSource());
engine.render(event);
}
/**

View file

@ -10,9 +10,8 @@ public interface RenderDispatcher {
* Render every model for every material.
*
* @param event Context for rendering.
* @param buffers The buffer source for which batched rendering should happen.
*/
void render(RenderLayerEvent event, MultiBufferSource buffers);
void render(RenderLayerEvent event);
/**
* Maintain the integer origin coordinate to be within a certain distance from the camera in all directions.

View file

@ -28,9 +28,9 @@ public class BatchedMaterial<D extends InstanceData> implements Material<D> {
return models.computeIfAbsent(key, $ -> new CPUInstancer<>(type, modelSupplier.get()));
}
public void render(PoseStack stack, VertexConsumer buffer, FormatContext context) {
public void setupAndRenderInto(PoseStack stack, VertexConsumer buffer) {
for (CPUInstancer<D> instancer : models.values()) {
instancer.setup(context);
instancer.setup();
instancer.drawAll(stack, buffer);
}
}

View file

@ -42,41 +42,44 @@ public class BatchedMaterialGroup implements MaterialGroup {
if (buffer instanceof DirectBufferBuilder direct) {
renderParallel(stack, pool, direct);
} else {
renderSerial(stack, buffer, FormatContext.defaultContext());
renderSerial(stack, buffer);
}
}
private void renderParallel(PoseStack stack, TaskEngine pool, DirectBufferBuilder direct) {
int vertexCount = calculateNeededVertices();
int vertexCount = 0;
for (BatchedMaterial<?> material : materials.values()) {
for (CPUInstancer<?> instancer : material.models.values()) {
instancer.setup();
vertexCount += instancer.getTotalVertexCount();
}
}
DirectVertexConsumer consumer = direct.intoDirectConsumer(vertexCount);
FormatContext context = new FormatContext(consumer.hasOverlay());
// avoids rendering garbage, but doesn't fix the issue of some instances not being buffered
consumer.memSetZero();
for (BatchedMaterial<?> material : materials.values()) {
for (CPUInstancer<?> instancer : material.models.values()) {
instancer.setup(context);
if (consumer.hasOverlay()) {
instancer.sbb.context.fullNormalTransform = false;
instancer.sbb.context.outputColorDiffuse = false;
} else {
instancer.sbb.context.fullNormalTransform = false;
instancer.sbb.context.outputColorDiffuse = true;
}
instancer.submitTasks(stack, pool, consumer);
}
}
}
private void renderSerial(PoseStack stack, VertexConsumer consumer, FormatContext context) {
private void renderSerial(PoseStack stack, VertexConsumer consumer) {
for (BatchedMaterial<?> value : materials.values()) {
value.render(stack, consumer, context);
value.setupAndRenderInto(stack, consumer);
}
}
private int calculateNeededVertices() {
int total = 0;
for (BatchedMaterial<?> material : materials.values()) {
for (CPUInstancer<?> instancer : material.models.values()) {
total += instancer.getTotalVertexCount();
}
}
return total;
}
public void clear() {
materials.values().forEach(BatchedMaterial::clear);
}

View file

@ -9,7 +9,9 @@ import com.jozufozu.flywheel.backend.RenderLayer;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.event.RenderLayerEvent;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.MultiBufferSource;
@ -17,8 +19,9 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
public class BatchingEngine implements Engine {
public class BatchingEngine implements Engine, MultiBufferSource {
protected final Map<RenderType, BufferBuilder> buffers = new HashMap<>();
protected final Map<RenderLayer, Map<RenderType, BatchedMaterialGroup>> layers;
protected final TaskEngine taskEngine;
@ -42,7 +45,7 @@ public class BatchingEngine implements Engine {
}
@Override
public void render(RenderLayerEvent event, MultiBufferSource buffers) {
public void render(RenderLayerEvent event) {
PoseStack stack = event.stack;
stack.pushPose();
@ -50,14 +53,27 @@ public class BatchingEngine implements Engine {
stack.translate(-event.camX, -event.camY, -event.camZ);
for (BatchedMaterialGroup group : layers.get(event.getLayer()).values()) {
group.render(stack, buffers, taskEngine);
group.render(stack, this, taskEngine);
}
taskEngine.syncPoint();
stack.popPose();
event.buffers.bufferSource().endBatch();
// TODO: when/if this causes trouble with shaders, try to inject our BufferBuilders
// into the RenderBuffers from context.
buffers.forEach((type, builder) -> type.end(builder, 0, 0, 0));
}
@Override
public VertexConsumer getBuffer(RenderType renderType) {
BufferBuilder builder = buffers.computeIfAbsent(renderType, type -> new BufferBuilder(type.bufferSize()));
if (!builder.building()) {
builder.begin(renderType.mode(), renderType.format());
}
return builder;
}
@Override

View file

@ -2,12 +2,12 @@ package com.jozufozu.flywheel.backend.instancing.batching;
import com.jozufozu.flywheel.api.InstanceData;
import com.jozufozu.flywheel.api.struct.Batched;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.instancing.AbstractInstancer;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.backend.model.DirectVertexConsumer;
import com.jozufozu.flywheel.core.model.Model;
import com.jozufozu.flywheel.core.model.ModelTransformer;
import com.jozufozu.flywheel.util.Color;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
@ -15,15 +15,14 @@ public class CPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
private final Batched<D> batchingType;
private final ModelTransformer sbb;
private final ModelTransformer.Params defaultParams;
final ModelTransformer sbb;
public CPUInstancer(Batched<D> type, Model modelData) {
super(type::create, modelData);
batchingType = type;
sbb = new ModelTransformer(modelData);
defaultParams = ModelTransformer.Params.defaultParams();
modelData.configure(sbb.context);
}
void submitTasks(PoseStack stack, TaskEngine pool, DirectVertexConsumer consumer) {
@ -42,43 +41,47 @@ public class CPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
}
}
@Override
public void notifyDirty() {
// noop
}
private void drawRange(PoseStack stack, VertexConsumer buffer, int from, int to) {
ModelTransformer.Params params = defaultParams.copy();
ModelTransformer.Params params = new ModelTransformer.Params();
// Color color = Color.generateFromLong(from);
for (D d : data.subList(from, to)) {
params.loadDefault();
batchingType.transform(d, params);
sbb.renderInto(params, stack, buffer);
//params.color(color.getRGB());
params.load(defaultParams);
sbb.renderInto(params, stack, buffer);
}
}
void drawAll(PoseStack stack, VertexConsumer buffer) {
ModelTransformer.Params params = defaultParams.copy();
ModelTransformer.Params params = new ModelTransformer.Params();
for (D d : data) {
params.loadDefault();
batchingType.transform(d, params);
sbb.renderInto(params, stack, buffer);
params.load(defaultParams);
}
}
void setup(FormatContext context) {
void setup() {
if (anyToRemove) {
removeDeletedInstances();
data.removeIf(InstanceData::isRemoved);
anyToRemove = false;
}
if (context.usesOverlay()) {
defaultParams.overlay();
if (false) {
this.sbb.context.outputColorDiffuse = false;
this.sbb.context.fullNormalTransform = false;
}
}
@Override
public void notifyDirty() {
// noop
}
}

View file

@ -1,8 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.batching;
public record FormatContext(boolean usesOverlay) {
public static FormatContext defaultContext() {
return new FormatContext(false);
}
}

View file

@ -14,8 +14,8 @@ import com.jozufozu.flywheel.backend.RenderWork;
import com.jozufozu.flywheel.backend.model.ImmediateAllocator;
import com.jozufozu.flywheel.backend.model.ModelAllocator;
import com.jozufozu.flywheel.backend.model.ModelPool;
import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.core.model.Model;
import com.jozufozu.flywheel.core.vertex.PosNormalTexType;
/**
* A collection of Instancers that all have the same format.
@ -33,7 +33,7 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
if (Backend.getInstance().compat.onAMDWindows()) {
allocator = ImmediateAllocator.INSTANCE;
} else {
allocator = new ModelPool(Formats.UNLIT_MODEL, 64);
allocator = new ModelPool(PosNormalTexType.INSTANCE, 64);
}
this.models = CacheBuilder.newBuilder()
.removalListener(notification -> {

View file

@ -82,7 +82,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
* Render every model for every material.
*/
@Override
public void render(RenderLayerEvent event, MultiBufferSource buffers) {
public void render(RenderLayerEvent event) {
double camX;
double camY;
double camZ;

View file

@ -34,7 +34,7 @@ public class BufferedModel implements IBufferedModel {
// mirror it in system memory so we can write to it, and upload our model.
try (MappedBuffer buffer = vbo.getBuffer(0, model.size())) {
model.buffer(new VecBufferWriter(buffer));
model.getType().copyInto(buffer.unwrap(), model.getReader());
} catch (Exception e) {
Flywheel.log.error(String.format("Error uploading model '%s':", model.name()), e);
}

View file

@ -1,6 +1,20 @@
package com.jozufozu.flywheel.backend.model;
import com.mojang.blaze3d.vertex.BufferBuilder;
/**
* Duck interface used on {@link BufferBuilder} to provide lower level access to the backing memory.
*/
public interface DirectBufferBuilder {
/**
* Create a DirectVertexConsumer from this BufferBuilder.
*
* <p>
* After this function returns, the internal state of the BufferBuilder will be as if
* {@link BufferBuilder#endVertex()} was called vertexCount times. It is up to the callee
* to actually populate the BufferBuilder with vertices using the returned value.
* </p>
*/
DirectVertexConsumer intoDirectConsumer(int vertexCount);
}

View file

@ -10,6 +10,11 @@ import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.VertexFormatElement;
/**
* An unsafe vertex consumer allowing for unchecked writes into a ByteBuffer.
*
* @see DirectBufferBuilder
*/
public class DirectVertexConsumer implements VertexConsumer {
public final VertexFormat format;
private final int stride;
@ -68,12 +73,16 @@ public class DirectVertexConsumer implements VertexConsumer {
this.end = parent.vertexBase + (long) maxVertices * this.stride;
}
public void memSetZero() {
MemoryUtil.memSet(vertexBase, 0, end - vertexBase);
}
public boolean hasOverlay() {
return uv1 >= 0;
}
/**
* Split off the head of this consumer into a new object and advance our write-pointer.
* Split off the head of this consumer into a new object and advance this object's write-pointer.
* @param vertexCount The number of vertices that must be written to the head.
* @return The head of this consumer.
*/
@ -102,8 +111,8 @@ public class DirectVertexConsumer implements VertexConsumer {
public VertexConsumer color(int r, int g, int b, int a) {
if (color < 0) return this;
long base = vertexBase + color;
int color = ((r & 0xFF)) | ((g & 0xFF) << 8) | ((b & 0xFF) << 16) | ((a & 0xFF) << 24);
//MemoryUtil.memPutInt(base, color);
// int color = ((r & 0xFF)) | ((g & 0xFF) << 8) | ((b & 0xFF) << 16) | ((a & 0xFF) << 24);
// MemoryUtil.memPutInt(base, color);
MemoryUtil.memPutByte(base, (byte) r);
MemoryUtil.memPutByte(base + 1, (byte) g);
MemoryUtil.memPutByte(base + 2, (byte) b);

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.backend.model;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@ -13,12 +14,12 @@ import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.MappedGlBuffer;
import com.jozufozu.flywheel.core.model.Model;
import com.jozufozu.flywheel.core.model.VecBufferWriter;
import com.jozufozu.flywheel.core.vertex.VertexType;
import com.jozufozu.flywheel.util.AttribUtil;
public class ModelPool implements ModelAllocator {
protected final VertexFormat format;
protected final VertexType vertexType;
private final List<PooledModel> models = new ArrayList<>();
@ -32,9 +33,9 @@ public class ModelPool implements ModelAllocator {
private boolean dirty;
private boolean anyToRemove;
public ModelPool(VertexFormat format, int initialSize) {
this.format = format;
bufferSize = format.getStride() * initialSize;
public ModelPool(VertexType vertexType, int initialSize) {
this.vertexType = vertexType;
bufferSize = vertexType.getStride() * initialSize;
vbo = new MappedGlBuffer(GlBufferType.ARRAY_BUFFER);
@ -104,7 +105,7 @@ public class ModelPool implements ModelAllocator {
* @return true if the buffer was reallocated
*/
private boolean realloc() {
int neededSize = vertices * format.getStride();
int neededSize = vertices * vertexType.getStride();
if (neededSize > bufferSize) {
bufferSize = neededSize + 128;
vbo.alloc(bufferSize);
@ -117,14 +118,14 @@ public class ModelPool implements ModelAllocator {
private void uploadAll() {
try (MappedBuffer buffer = vbo.getBuffer(0, bufferSize)) {
VecBufferWriter consumer = new VecBufferWriter(buffer);
ByteBuffer buf = buffer.unwrap();
int vertices = 0;
for (PooledModel model : models) {
model.first = vertices;
model.model.buffer(consumer);
if (model.callback != null) model.callback.onAlloc(model);
buffer(buf, model);
vertices += model.getVertexCount();
}
@ -135,14 +136,9 @@ public class ModelPool implements ModelAllocator {
private void uploadPending() {
try (MappedBuffer buffer = vbo.getBuffer(0, bufferSize)) {
VecBufferWriter consumer = new VecBufferWriter(buffer);
int stride = format.getStride();
ByteBuffer buf = buffer.unwrap();
for (PooledModel model : pendingUpload) {
int pos = model.first * stride;
buffer.position(pos);
model.model.buffer(consumer);
if (model.callback != null) model.callback.onAlloc(model);
buffer(buf, model);
}
pendingUpload.clear();
} catch (Exception e) {
@ -150,6 +146,13 @@ public class ModelPool implements ModelAllocator {
}
}
private void buffer(ByteBuffer buf, PooledModel model) {
int pos = model.first * vertexType.getStride();
buf.position(pos);
vertexType.copyInto(buf, model.model.getReader());
if (model.callback != null) model.callback.onAlloc(model);
}
private void setDirty() {
dirty = true;
}

View file

@ -6,7 +6,7 @@ import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
public class Formats {
public static final VertexFormat UNLIT_MODEL = VertexFormat.builder()
.addAttributes(CommonAttributes.VEC3, CommonAttributes.NORMAL, CommonAttributes.UV)
.addAttributes(CommonAttributes.VEC3, CommonAttributes.UV, CommonAttributes.NORMAL)
.build();
public static final VertexFormat COLORED_LIT_MODEL = VertexFormat.builder()

View file

@ -77,7 +77,7 @@ public class CrumblingRenderer {
instanceManager.beginFrame(info);
materials.render(event, null);
materials.render(event);
instanceManager.invalidate();
}

View file

@ -1,12 +1,8 @@
package com.jozufozu.flywheel.core.model;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.util.BufferBuilderReader;
import com.jozufozu.flywheel.util.UnsafeBlockFormatReader;
import com.jozufozu.flywheel.util.ModelReader;
import com.jozufozu.flywheel.util.RenderMath;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
@ -33,38 +29,27 @@ public class BlockModel implements Model {
}
public BlockModel(BakedModel model, BlockState referenceState, PoseStack ms) {
reader = new BufferBuilderReader(ModelUtil.getBufferBuilder(model, referenceState, ms));
reader = new UnsafeBlockFormatReader(ModelUtil.getBufferBuilder(model, referenceState, ms));
name = referenceState.toString();
}
@Override
public void configure(ModelTransformer.Context ctx) {
ctx.inputHasDiffuse = true;
}
@Override
public String name() {
return name;
}
@Override
public VertexFormat format() {
return Formats.UNLIT_MODEL;
}
@Override
public int vertexCount() {
return reader.getVertexCount();
}
@Override
public void buffer(VertexConsumer buffer) {
int vertexCount = vertexCount();
for (int i = 0; i < vertexCount; i++) {
buffer.vertex(reader.getX(i), reader.getY(i), reader.getZ(i));
buffer.normal(reader.getNX(i), reader.getNY(i), reader.getNZ(i));
buffer.uv(reader.getU(i), reader.getV(i));
buffer.endVertex();
}
public ModelReader getReader() {
return reader;
}
}

View file

@ -0,0 +1,71 @@
package com.jozufozu.flywheel.core.model;
import java.nio.ByteBuffer;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.core.vertex.VertexType;
import com.jozufozu.flywheel.util.ModelReader;
import com.jozufozu.flywheel.util.RenderMath;
import net.minecraft.client.renderer.LightTexture;
public class BlockType implements VertexType {
public static final BlockType INSTANCE = new BlockType();
@Override
public VertexFormat getFormat() {
return Formats.COLORED_LIT_MODEL;
}
@Override
public void copyInto(ByteBuffer buffer, ModelReader reader) {
int stride = getStride();
long addr = MemoryUtil.memAddress(buffer);
int vertexCount = reader.getVertexCount();
for (int i = 0; i < vertexCount; i++) {
float x = reader.getX(i);
float y = reader.getY(i);
float z = reader.getZ(i);
float xN = reader.getNX(i);
float yN = reader.getNY(i);
float zN = reader.getNZ(i);
float u = reader.getU(i);
float v = reader.getV(i);
byte r = reader.getR(i);
byte g = reader.getG(i);
byte b = reader.getB(i);
byte a = reader.getA(i);
int light = reader.getLight(i);
MemoryUtil.memPutFloat(addr, x);
MemoryUtil.memPutFloat(addr + 4, y);
MemoryUtil.memPutFloat(addr + 8, z);
MemoryUtil.memPutByte(addr + 12, RenderMath.nb(xN));
MemoryUtil.memPutByte(addr + 13, RenderMath.nb(yN));
MemoryUtil.memPutByte(addr + 14, RenderMath.nb(zN));
MemoryUtil.memPutFloat(addr + 15, u);
MemoryUtil.memPutFloat(addr + 19, v);
MemoryUtil.memPutByte(addr + 23, r);
MemoryUtil.memPutByte(addr + 24, g);
MemoryUtil.memPutByte(addr + 25, b);
MemoryUtil.memPutByte(addr + 26, a);
byte block = (byte) (LightTexture.block(light) << 4);
byte sky = (byte) (LightTexture.sky(light) << 4);
MemoryUtil.memPutByte(addr + 27, block);
MemoryUtil.memPutByte(addr + 28, sky);
addr += stride;
}
}
}

View file

@ -3,8 +3,9 @@ package com.jozufozu.flywheel.core.model;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.model.ElementBuffer;
import com.jozufozu.flywheel.core.QuadConverter;
import com.jozufozu.flywheel.core.vertex.PosNormalTexType;
import com.jozufozu.flywheel.core.vertex.VertexType;
import com.jozufozu.flywheel.util.ModelReader;
import com.mojang.blaze3d.vertex.VertexConsumer;
/**
* A model that can be rendered by flywheel.
@ -33,20 +34,27 @@ public interface Model {
*/
String name();
/**
* Copy this model into the given buffer.
*/
void buffer(VertexConsumer buffer);
ModelReader getReader();
/**
* @return The number of vertices the model has.
*/
int vertexCount();
default void configure(ModelTransformer.Context ctx) {
}
default VertexType getType() {
return PosNormalTexType.INSTANCE;
}
/**
* @return The format of this model's vertices
*/
VertexFormat format();
default VertexFormat format() {
return getType().getFormat();
}
/**
* Create an element buffer object that indexes the vertices of this model.
@ -77,6 +85,4 @@ public interface Model {
default boolean empty() {
return vertexCount() == 0;
}
ModelReader getReader();
}

View file

@ -3,29 +3,35 @@ package com.jozufozu.flywheel.core.model;
import java.nio.ByteBuffer;
import java.util.List;
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.core.vertex.PosNormalTexReader;
import com.jozufozu.flywheel.core.vertex.PosTexNormalWriter;
import com.jozufozu.flywheel.util.ModelReader;
import com.jozufozu.flywheel.util.RenderMath;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Vector3f;
import com.mojang.blaze3d.platform.MemoryTracker;
public class ModelPart implements Model {
private final List<PartBuilder.CuboidBuilder> cuboids;
private int vertices;
private final int vertices;
private final String name;
private final PosNormalTexReader reader;
public ModelPart(List<PartBuilder.CuboidBuilder> cuboids, String name) {
this.cuboids = cuboids;
this.name = name;
vertices = 0;
for (PartBuilder.CuboidBuilder cuboid : cuboids) {
vertices += cuboid.vertices();
{
int vertices = 0;
for (PartBuilder.CuboidBuilder cuboid : cuboids) {
vertices += cuboid.vertices();
}
this.vertices = vertices;
}
ByteBuffer buffer = MemoryTracker.create(size());
PosTexNormalWriter writer = new PosTexNormalWriter(buffer);
for (PartBuilder.CuboidBuilder cuboid : cuboids) {
cuboid.buffer(writer);
}
reader = new PosNormalTexReader(buffer, vertices);
}
public static PartBuilder builder(String name, int sizeU, int sizeV) {
@ -37,111 +43,13 @@ public class ModelPart implements Model {
return name;
}
@Override
public void buffer(VertexConsumer buffer) {
for (PartBuilder.CuboidBuilder cuboid : cuboids) {
cuboid.buffer(buffer);
}
}
@Override
public int vertexCount() {
return vertices;
}
@Override
public VertexFormat format() {
return Formats.UNLIT_MODEL;
}
@Override
public ModelReader getReader() {
return new PartReader(this);
}
private class PartReader implements ModelReader {
private final ByteBuffer buffer;
private PartReader(ModelPart part) {
this.buffer = ByteBuffer.allocate(part.size());
VecBufferWriter writer = new VecBufferWriter(this.buffer);
buffer(writer);
}
private int vertIdx(int vertexIndex) {
return vertexIndex * format().getStride();
}
@Override
public float getX(int index) {
return buffer.getFloat(vertIdx(index));
}
@Override
public float getY(int index) {
return buffer.getFloat(vertIdx(index) + 4);
}
@Override
public float getZ(int index) {
return buffer.getFloat(vertIdx(index) + 8);
}
@Override
public byte getR(int index) {
return (byte) 0xFF;
}
@Override
public byte getG(int index) {
return (byte) 0xFF;
}
@Override
public byte getB(int index) {
return (byte) 0xFF;
}
@Override
public byte getA(int index) {
return (byte) 0xFF;
}
@Override
public float getU(int index) {
return buffer.getFloat(vertIdx(index) + 15);
}
@Override
public float getV(int index) {
return buffer.getFloat(vertIdx(index) + 19);
}
@Override
public int getLight(int index) {
return 0;
}
@Override
public float getNX(int index) {
return RenderMath.f(buffer.get(vertIdx(index) + 12));
}
@Override
public float getNY(int index) {
return RenderMath.f(buffer.get(vertIdx(index) + 13));
}
@Override
public float getNZ(int index) {
return RenderMath.f(buffer.get(vertIdx(index) + 14));
}
@Override
public int getVertexCount() {
return vertices;
}
return reader;
}
}

View file

@ -1,6 +1,7 @@
package com.jozufozu.flywheel.core.model;
import com.jozufozu.flywheel.util.ModelReader;
import com.jozufozu.flywheel.util.RenderMath;
import com.jozufozu.flywheel.util.transform.Transform;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
@ -16,6 +17,8 @@ public class ModelTransformer {
private final Model model;
private final ModelReader reader;
public final Context context = new Context();
public ModelTransformer(Model model) {
this.model = model;
reader = model.getReader();
@ -34,7 +37,7 @@ public class ModelTransformer {
modelMat.multiply(params.model);
Matrix3f normalMat;
if (params.fullNormalTransform) {
if (context.fullNormalTransform) {
normalMat = input.last().normal().copy();
normalMat.mul(params.normal);
} else {
@ -61,42 +64,46 @@ public class ModelTransformer {
float ny = normal.y();
float nz = normal.z();
float instanceDiffuse = LightUtil.diffuseLight(nx, ny, nz);
switch (params.colorMode) {
case MODEL_ONLY -> builder.color(reader.getR(i), reader.getG(i), reader.getB(i), reader.getA(i));
case DIFFUSE_ONLY -> builder.color(instanceDiffuse, instanceDiffuse, instanceDiffuse, 1f);
case MODEL_DIFFUSE -> {
int r = Byte.toUnsignedInt(reader.getR(i));
int g = Byte.toUnsignedInt(reader.getG(i));
int b = Byte.toUnsignedInt(reader.getB(i));
int a = Byte.toUnsignedInt(reader.getA(i));
float diffuse = switch (params.diffuseMode) {
case NONE -> 1f;
case INSTANCE -> instanceDiffuse;
case ONE_OVER_STATIC -> 1 / LightUtil.diffuseLight(normalX, normalY, normalZ);
case INSTANCE_OVER_STATIC -> instanceDiffuse / LightUtil.diffuseLight(normalX, normalY, normalZ);
};
if (diffuse != 1) {
r = transformColor(r, diffuse);
g = transformColor(g, diffuse);
b = transformColor(b, diffuse);
}
builder.color(r, g, b, a);
}
case RECOLOR -> {
if (params.diffuseMode == DiffuseMode.NONE) {
builder.color(params.r, params.g, params.b, params.a);
} else {
if (params.useParamColor) {
if (context.outputColorDiffuse) {
float instanceDiffuse = LightUtil.diffuseLight(nx, ny, nz);
int colorR = transformColor(params.r, instanceDiffuse);
int colorG = transformColor(params.g, instanceDiffuse);
int colorB = transformColor(params.b, instanceDiffuse);
builder.color(colorR, colorG, colorB, params.a);
} else {
builder.color(params.r, params.g, params.b, params.a);
}
} else {
if (context.inputHasDiffuse) {
int r = Byte.toUnsignedInt(reader.getR(i));
int g = Byte.toUnsignedInt(reader.getG(i));
int b = Byte.toUnsignedInt(reader.getB(i));
int a = Byte.toUnsignedInt(reader.getA(i));
float undoStaticDiffuse = 1 / LightUtil.diffuseLight(normalX, normalY, normalZ);
float diffuse;
if (context.outputColorDiffuse) {
diffuse = LightUtil.diffuseLight(nx, ny, nz) * undoStaticDiffuse;
} else {
diffuse = undoStaticDiffuse;
}
if (diffuse != 1) {
r = transformColor(r, diffuse);
g = transformColor(g, diffuse);
b = transformColor(b, diffuse);
}
builder.color(r, g, b, a);
} else {
if (context.outputColorDiffuse) {
int d = RenderMath.unb(LightUtil.diffuseLight(nx, ny, nz));
builder.color(d, d, d, 0xFF);
} else {
builder.color(reader.getR(i), reader.getG(i), reader.getB(i), reader.getA(i));
}
}
}
}
//builder.color(Math.max(0, (int) (nx * 255)), Math.max(0, (int) (ny * 255)), Math.max(0, (int) (nz * 255)), 0xFF);
@ -110,11 +117,10 @@ public class ModelTransformer {
builder.uv(u, v);
}
if (params.hasOverlay) {
builder.overlayCoords(params.overlay);
}
// not always used, but will be ignored by formats that don't use it
builder.overlayCoords(params.overlay);
builder.uv2(params.hasCustomLight ? params.packedLightCoords : reader.getLight(i));
builder.uv2(params.useParamLight ? params.packedLightCoords : reader.getLight(i));
builder.normal(nx, ny, nz);
@ -128,7 +134,7 @@ public class ModelTransformer {
@Override
public String toString() {
return "SuperByteBuffer[" + model + ']';
return "ModelTransformer[" + model + ']';
}
public static int transformColor(int component, float scale) {
@ -140,28 +146,32 @@ public class ModelTransformer {
void shift(VertexConsumer builder, float u, float v);
}
public enum ColorMode {
MODEL_ONLY,
MODEL_DIFFUSE,
DIFFUSE_ONLY,
RECOLOR
}
public static class Context {
/**
* Do we need to include the PoseStack transforms in our transformation of the normal?
*/
public boolean fullNormalTransform = false;
/**
* Does the model we're transforming have diffuse light baked into its colors?
*/
public boolean inputHasDiffuse = false;
/**
* Do we need to bake diffuse lighting into the output colors?
*/
public boolean outputColorDiffuse = true;
public enum DiffuseMode {
NONE,
INSTANCE,
ONE_OVER_STATIC,
INSTANCE_OVER_STATIC,
}
public static class Params implements Transform<Params> {
// Vertex Position
// Transform
public final Matrix4f model;
public final Matrix3f normal;
// Vertex Coloring
public ColorMode colorMode;
public DiffuseMode diffuseMode;
public boolean useParamColor;
public int r;
public int g;
public int b;
@ -171,46 +181,47 @@ public class ModelTransformer {
public SpriteShiftFunc spriteShiftFunc;
// Vertex Overlay Color
public boolean hasOverlay;
public int overlay;
// Vertex Lighting
public boolean hasCustomLight;
public boolean useParamLight;
public int packedLightCoords;
// Vertex Normals
public boolean fullNormalTransform;
public Params() {
model = new Matrix4f();
normal = new Matrix3f();
}
public void loadDefault() {
model.setIdentity();
normal.setIdentity();
useParamColor = true;
r = 0xFF;
g = 0xFF;
b = 0xFF;
a = 0xFF;
spriteShiftFunc = null;
overlay = OverlayTexture.NO_OVERLAY;
useParamLight = false;
packedLightCoords = LightTexture.FULL_BRIGHT;
}
public void load(Params from) {
model.load(from.model);
normal.load(from.normal);
colorMode = from.colorMode;
diffuseMode = from.diffuseMode;
useParamColor = from.useParamColor;
r = from.r;
g = from.g;
b = from.b;
a = from.a;
spriteShiftFunc = from.spriteShiftFunc;
hasOverlay = from.hasOverlay;
overlay = from.overlay;
hasCustomLight = from.hasCustomLight;
useParamLight = from.useParamLight;
packedLightCoords = from.packedLightCoords;
fullNormalTransform = from.fullNormalTransform;
}
public Params copy() {
Params params = new Params();
params.load(this);
return params;
}
public Params color(int r, int g, int b, int a) {
this.colorMode = ColorMode.RECOLOR;
this.useParamColor = true;
this.r = r;
this.g = g;
this.b = b;
@ -219,7 +230,7 @@ public class ModelTransformer {
}
public Params color(byte r, byte g, byte b, byte a) {
this.colorMode = ColorMode.RECOLOR;
this.useParamColor = true;
this.r = Byte.toUnsignedInt(r);
this.g = Byte.toUnsignedInt(g);
this.b = Byte.toUnsignedInt(b);
@ -228,7 +239,7 @@ public class ModelTransformer {
}
public Params color(int color) {
this.colorMode = ColorMode.RECOLOR;
this.useParamColor = true;
this.r = ((color >> 16) & 0xFF);
this.g = ((color >> 8) & 0xFF);
this.b = (color & 0xFF);
@ -241,29 +252,13 @@ public class ModelTransformer {
return this;
}
public Params overlay() {
this.hasOverlay = true;
return this;
}
public Params overlay(int overlay) {
this.hasOverlay = true;
this.overlay = overlay;
return this;
}
/**
* Transforms normals not only by the local matrix stack, but also by the passed matrix stack.
*/
public void entityMode() {
this.hasOverlay = true;
this.fullNormalTransform = true;
this.diffuseMode = DiffuseMode.NONE;
this.colorMode = ColorMode.RECOLOR;
}
public Params light(int packedLightCoords) {
this.hasCustomLight = true;
this.useParamLight = true;
this.packedLightCoords = packedLightCoords;
return this;
}
@ -313,23 +308,5 @@ public class ModelTransformer {
return this;
}
public static Params defaultParams() {
Params out = new Params();
out.model.setIdentity();
out.normal.setIdentity();
out.colorMode = ColorMode.DIFFUSE_ONLY;
out.diffuseMode = DiffuseMode.INSTANCE;
out.r = 0xFF;
out.g = 0xFF;
out.b = 0xFF;
out.a = 0xFF;
out.spriteShiftFunc = null;
out.hasOverlay = false;
out.overlay = OverlayTexture.NO_OVERLAY;
out.hasCustomLight = false;
out.packedLightCoords = LightTexture.FULL_BRIGHT;
out.fullNormalTransform = false;
return out;
}
}
}

View file

@ -5,7 +5,7 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.jozufozu.flywheel.core.vertex.PosTexNormalWriter;
import com.mojang.math.Matrix3f;
import com.mojang.math.Quaternion;
import com.mojang.math.Vector3f;
@ -160,7 +160,7 @@ public class PartBuilder {
return visibleFaces.size() * 4;
}
public void buffer(VertexConsumer buffer) {
public void buffer(PosTexNormalWriter buffer) {
float sizeX = posX2 - posX1;
float sizeY = posY2 - posY1;
@ -235,11 +235,11 @@ public class PartBuilder {
}
}
public void quad(VertexConsumer buffer, Vector3f[] vertices, float minU, float minV, float maxU, float maxV, Vector3f normal) {
buffer.vertex(vertices[0].x(), vertices[0].y(), vertices[0].z()).normal(normal.x(), normal.y(), normal.z()).uv(maxU, minV).endVertex();
buffer.vertex(vertices[1].x(), vertices[1].y(), vertices[1].z()).normal(normal.x(), normal.y(), normal.z()).uv(minU, minV).endVertex();
buffer.vertex(vertices[2].x(), vertices[2].y(), vertices[2].z()).normal(normal.x(), normal.y(), normal.z()).uv(minU, maxV).endVertex();
buffer.vertex(vertices[3].x(), vertices[3].y(), vertices[3].z()).normal(normal.x(), normal.y(), normal.z()).uv(maxU, maxV).endVertex();
public void quad(PosTexNormalWriter buffer, Vector3f[] vertices, float minU, float minV, float maxU, float maxV, Vector3f normal) {
buffer.putVertex(vertices[0].x(), vertices[0].y(), vertices[0].z(), normal.x(), normal.y(), normal.z(), maxU, minV);
buffer.putVertex(vertices[1].x(), vertices[1].y(), vertices[1].z(), normal.x(), normal.y(), normal.z(), minU, minV);
buffer.putVertex(vertices[2].x(), vertices[2].y(), vertices[2].z(), normal.x(), normal.y(), normal.z(), minU, maxV);
buffer.putVertex(vertices[3].x(), vertices[3].y(), vertices[3].z(), normal.x(), normal.y(), normal.z(), maxU, maxV);
}

View file

@ -2,14 +2,10 @@ package com.jozufozu.flywheel.core.model;
import java.util.Collection;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.util.BufferBuilderReader;
import com.jozufozu.flywheel.core.vertex.VertexType;
import com.jozufozu.flywheel.util.UnsafeBlockFormatReader;
import com.jozufozu.flywheel.util.ModelReader;
import com.jozufozu.flywheel.util.RenderMath;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
@ -20,7 +16,7 @@ public class WorldModel implements Model {
private final String name;
public WorldModel(BlockAndTintGetter renderWorld, RenderType layer, Collection<StructureTemplate.StructureBlockInfo> blocks, String name) {
reader = new BufferBuilderReader(ModelUtil.getBufferBuilderFromTemplate(renderWorld, layer, blocks));
reader = new UnsafeBlockFormatReader(ModelUtil.getBufferBuilderFromTemplate(renderWorld, layer, blocks));
this.name = name;
}
@ -30,25 +26,8 @@ public class WorldModel implements Model {
}
@Override
public void buffer(VertexConsumer vertices) {
for (int i = 0; i < vertexCount(); i++) {
vertices.vertex(reader.getX(i), reader.getY(i), reader.getZ(i));
vertices.normal(reader.getNX(i), reader.getNY(i), reader.getNZ(i));
vertices.uv(reader.getU(i), reader.getV(i));
vertices.color(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.uv2(block, sky);
vertices.endVertex();
}
public VertexType getType() {
return BlockType.INSTANCE;
}
@Override
@ -56,11 +35,6 @@ public class WorldModel implements Model {
return reader.getVertexCount();
}
@Override
public VertexFormat format() {
return Formats.COLORED_LIT_MODEL;
}
@Override
public ModelReader getReader() {
return reader;

View file

@ -0,0 +1,97 @@
package com.jozufozu.flywheel.core.vertex;
import java.nio.ByteBuffer;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.util.ModelReader;
import com.jozufozu.flywheel.util.RenderMath;
import net.minecraft.client.renderer.LightTexture;
public class PosNormalTexReader implements ModelReader {
private final ByteBuffer buffer;
private final int vertexCount;
private final long base;
public PosNormalTexReader(ByteBuffer buffer, int vertexCount) {
this.buffer = buffer;
this.vertexCount = vertexCount;
this.base = MemoryUtil.memAddress(buffer);
}
private long ptr(long idx) {
return base + idx * 23;
}
@Override
public float getX(int index) {
return MemoryUtil.memGetFloat(ptr(index));
}
@Override
public float getY(int index) {
return MemoryUtil.memGetFloat(ptr(index) + 4);
}
@Override
public float getZ(int index) {
return MemoryUtil.memGetFloat(ptr(index) + 8);
}
@Override
public byte getR(int index) {
return (byte) 0xFF;
}
@Override
public byte getG(int index) {
return (byte) 0xFF;
}
@Override
public byte getB(int index) {
return (byte) 0xFF;
}
@Override
public byte getA(int index) {
return (byte) 0xFF;
}
@Override
public float getU(int index) {
return MemoryUtil.memGetFloat(ptr(index) + 12);
}
@Override
public float getV(int index) {
return MemoryUtil.memGetFloat(ptr(index) + 16);
}
@Override
public int getLight(int index) {
return 0;
}
@Override
public float getNX(int index) {
return RenderMath.f(MemoryUtil.memGetByte(ptr(index) + 20));
}
@Override
public float getNY(int index) {
return RenderMath.f(MemoryUtil.memGetByte(ptr(index) + 21));
}
@Override
public float getNZ(int index) {
return RenderMath.f(MemoryUtil.memGetByte(ptr(index) + 22));
}
@Override
public int getVertexCount() {
return vertexCount;
}
}

View file

@ -0,0 +1,38 @@
package com.jozufozu.flywheel.core.vertex;
import java.nio.ByteBuffer;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.util.ModelReader;
public class PosNormalTexType implements VertexType {
public static final PosNormalTexType INSTANCE = new PosNormalTexType();
@Override
public VertexFormat getFormat() {
return Formats.UNLIT_MODEL;
}
@Override
public void copyInto(ByteBuffer buffer, ModelReader reader) {
PosTexNormalWriter writer = new PosTexNormalWriter(buffer);
int vertexCount = reader.getVertexCount();
for (int i = 0; i < vertexCount; i++) {
float x = reader.getX(i);
float y = reader.getY(i);
float z = reader.getZ(i);
float u = reader.getU(i);
float v = reader.getV(i);
float xN = reader.getNX(i);
float yN = reader.getNY(i);
float zN = reader.getNZ(i);
writer.putVertex(x, y, z, xN, yN, zN, u, v);
}
}
}

View file

@ -0,0 +1,36 @@
package com.jozufozu.flywheel.core.vertex;
import java.nio.ByteBuffer;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.util.RenderMath;
public class PosTexNormalWriter {
private long addr;
private int vertexCount;
public PosTexNormalWriter(ByteBuffer buffer) {
addr = MemoryUtil.memAddress(buffer);
}
public void putVertex(float x, float y, float z, float nX, float nY, float nZ, float u, float v) {
MemoryUtil.memPutFloat(addr, x);
MemoryUtil.memPutFloat(addr + 4, y);
MemoryUtil.memPutFloat(addr + 8, z);
MemoryUtil.memPutFloat(addr + 12, u);
MemoryUtil.memPutFloat(addr + 16, v);
MemoryUtil.memPutByte(addr + 20, RenderMath.nb(nX));
MemoryUtil.memPutByte(addr + 21, RenderMath.nb(nY));
MemoryUtil.memPutByte(addr + 22, RenderMath.nb(nZ));
addr += 23;
vertexCount++;
}
public int getVertexCount() {
return vertexCount;
}
}

View file

@ -0,0 +1,17 @@
package com.jozufozu.flywheel.core.vertex;
import java.nio.ByteBuffer;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.util.ModelReader;
public interface VertexType {
VertexFormat getFormat();
void copyInto(ByteBuffer buffer, ModelReader reader);
default int getStride() {
return getFormat().getStride();
}
}

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.mixin;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.spongepowered.asm.mixin.Mixin;
@ -38,9 +39,11 @@ public abstract class BufferBuilderMixin implements DirectBufferBuilder {
private int nextElementByte;
@Override
@Nonnull
public DirectVertexConsumer intoDirectConsumer(int vertexCount) {
int bytes = vertexCount * format.getVertexSize();
ensureCapacity(bytes);
// ensure we have capacity for one extra vertex, BufferBuilder does this on #endVertex
ensureCapacity(bytes + format.getVertexSize());
DirectVertexConsumer consumer = new DirectVertexConsumer(this.buffer, this.format, vertexCount);
@ -49,7 +52,7 @@ public abstract class BufferBuilderMixin implements DirectBufferBuilder {
.get(0);
this.elementIndex = 0;
this.nextElementByte += bytes;
this.buffer.position(consumer.startPos + bytes);
this.buffer.position(this.buffer.position() + bytes);
return consumer;
}

View file

@ -3,36 +3,24 @@ package com.jozufozu.flywheel.util;
import java.nio.ByteBuffer;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.datafixers.util.Pair;
public class BufferBuilderReader implements ModelReader {
public class BlockFormatReader implements ModelReader {
private final ByteBuffer buffer;
private final int vertexCount;
private final int formatSize;
private final int size;
private final int stride;
public BufferBuilderReader(BufferBuilder builder) {
VertexFormat vertexFormat = builder.getVertexFormat();
public BlockFormatReader(BufferBuilder builder) {
Pair<BufferBuilder.DrawState, ByteBuffer> data = builder.popNextBuffer();
buffer = data.getSecond();
formatSize = vertexFormat.getVertexSize();
stride = builder.getVertexFormat()
.getVertexSize();
vertexCount = data.getFirst()
.vertexCount();
size = vertexCount * formatSize;
// TODO: adjust the getters based on the input format
// ImmutableList<VertexFormatElement> elements = vertexFormat.getElements();
// for (int i = 0, size = elements.size(); i < size; i++) {
// VertexFormatElement element = elements.get(i);
// int offset = vertexFormat.getOffset(i);
//
// element.getUsage()
// }
}
@Override
@ -41,7 +29,7 @@ public class BufferBuilderReader implements ModelReader {
}
private int vertIdx(int vertexIndex) {
return vertexIndex * formatSize;
return vertexIndex * stride;
}
@Override
@ -114,7 +102,4 @@ public class BufferBuilderReader implements ModelReader {
return vertexCount;
}
public int getSize() {
return size;
}
}

View file

@ -0,0 +1,309 @@
package com.jozufozu.flywheel.util;
import java.util.function.UnaryOperator;
import javax.annotation.Nonnull;
import com.google.common.hash.Hashing;
import com.mojang.math.Vector3f;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
@SuppressWarnings("PointlessBitwiseExpression")
public class Color {
public final static Color TRANSPARENT_BLACK = new Color(0, 0, 0, 0).setImmutable();
public final static Color BLACK = new Color(0, 0, 0).setImmutable();
public final static Color WHITE = new Color(255, 255, 255).setImmutable();
public final static Color RED = new Color(255, 0, 0).setImmutable();
public final static Color GREEN = new Color(0, 255, 0).setImmutable();
public final static Color SPRING_GREEN = new Color(0, 255, 187).setImmutable();
protected boolean mutable = true;
protected int value;
public Color(int r, int g, int b) {
this(r, g, b, 0xff);
}
public Color(int r, int g, int b, int a) {
value = ((a & 0xff) << 24) |
((r & 0xff) << 16) |
((g & 0xff) << 8) |
((b & 0xff) << 0);
}
public Color(float r, float g, float b, float a) {
this(
(int) (0.5 + 0xff * Mth.clamp(r, 0, 1)),
(int) (0.5 + 0xff * Mth.clamp(g, 0, 1)),
(int) (0.5 + 0xff * Mth.clamp(b, 0, 1)),
(int) (0.5 + 0xff * Mth.clamp(a, 0, 1))
);
}
public Color(int rgba) {
value = rgba;
}
public Color(int rgb, boolean hasAlpha) {
if (hasAlpha) {
value = rgb;
} else {
value = rgb | 0xff_000000;
}
}
public Color copy() {
return copy(true);
}
public Color copy(boolean mutable) {
if (mutable)
return new Color(value);
else
return new Color(value).setImmutable();
}
/**
* Mark this color as immutable. Attempting to mutate this color in the future
* will instead cause a copy to be created that can me modified.
*/
public Color setImmutable() {
this.mutable = false;
return this;
}
/**
* @return the red component in the range 0-255.
* @see #getRGB
*/
public int getRed() {
return (getRGB() >> 16) & 0xff;
}
/**
* @return the green component in the range 0-255.
* @see #getRGB
*/
public int getGreen() {
return (getRGB() >> 8) & 0xff;
}
/**
* @return the blue component in the range 0-255.
* @see #getRGB
*/
public int getBlue() {
return (getRGB() >> 0) & 0xff;
}
/**
* @return the alpha component in the range 0-255.
* @see #getRGB
*/
public int getAlpha() {
return (getRGB() >> 24) & 0xff;
}
/**
* @return the red component in the range 0-1f.
*/
public float getRedAsFloat() {
return getRed() / 255f;
}
/**
* @return the green component in the range 0-1f.
*/
public float getGreenAsFloat() {
return getGreen() / 255f;
}
/**
* @return the blue component in the range 0-1f.
*/
public float getBlueAsFloat() {
return getBlue() / 255f;
}
/**
* @return the alpha component in the range 0-1f.
*/
public float getAlphaAsFloat() {
return getAlpha() / 255f;
}
/**
* Returns the RGB value representing this color
* (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue).
* @return the RGB value of the color
*/
public int getRGB() {
return value;
}
public Vec3 asVector() {
return new Vec3(getRedAsFloat(), getGreenAsFloat(), getBlueAsFloat());
}
public Vector3f asVectorF() {
return new Vector3f(getRedAsFloat(), getGreenAsFloat(), getBlueAsFloat());
}
public Color setRed(int r) {
return ensureMutable().setRedUnchecked(r);
}
public Color setGreen(int g) {
return ensureMutable().setGreenUnchecked(g);
}
public Color setBlue(int b) {
return ensureMutable().setBlueUnchecked(b);
}
public Color setAlpha(int a) {
return ensureMutable().setAlphaUnchecked(a);
}
public Color setRed(float r) {
return ensureMutable().setRedUnchecked((int) (0xff * Mth.clamp(r, 0, 1)));
}
public Color setGreen(float g) {
return ensureMutable().setGreenUnchecked((int) (0xff * Mth.clamp(g, 0, 1)));
}
public Color setBlue(float b) {
return ensureMutable().setBlueUnchecked((int) (0xff * Mth.clamp(b, 0, 1)));
}
public Color setAlpha(float a) {
return ensureMutable().setAlphaUnchecked((int) (0xff * Mth.clamp(a, 0, 1)));
}
public Color scaleAlpha(float factor) {
return ensureMutable().setAlphaUnchecked((int) (getAlpha() * Mth.clamp(factor, 0, 1)));
}
public Color mixWith(Color other, float weight) {
return ensureMutable()
.setRedUnchecked((int) (getRed() + (other.getRed() - getRed()) * weight))
.setGreenUnchecked((int) (getGreen() + (other.getGreen() - getGreen()) * weight))
.setBlueUnchecked((int) (getBlue() + (other.getBlue() - getBlue()) * weight))
.setAlphaUnchecked((int) (getAlpha() + (other.getAlpha() - getAlpha()) * weight));
}
public Color darker() {
int a = getAlpha();
return ensureMutable().mixWith(BLACK, .25f).setAlphaUnchecked(a);
}
public Color brighter() {
int a = getAlpha();
return ensureMutable().mixWith(WHITE, .25f).setAlphaUnchecked(a);
}
public Color setValue(int value) {
return ensureMutable().setValueUnchecked(value);
}
public Color modifyValue(UnaryOperator<Integer> function) {
int newValue = function.apply(value);
if (newValue == value)
return this;
return ensureMutable().setValueUnchecked(newValue);
}
// ********* //
protected Color ensureMutable() {
if (this.mutable)
return this;
return new Color(this.value);
}
protected Color setRedUnchecked(int r) {
this.value = (this.value & 0xff_00ffff) | ((r & 0xff) << 16);
return this;
}
protected Color setGreenUnchecked(int g) {
this.value = (this.value & 0xff_ff00ff) | ((g & 0xff) << 8);
return this;
}
protected Color setBlueUnchecked(int b) {
this.value = (this.value & 0xff_ffff00) | ((b & 0xff) << 0);
return this;
}
protected Color setAlphaUnchecked(int a) {
this.value = (this.value & 0x00_ffffff) | ((a & 0xff) << 24);
return this;
}
protected Color setValueUnchecked(int value) {
this.value = value;
return this;
}
// ********* //
public static Color mixColors(@Nonnull Color c1, @Nonnull Color c2, float w) {
return new Color(
(int) (c1.getRed() + (c2.getRed() - c1.getRed()) * w),
(int) (c1.getGreen() + (c2.getGreen() - c1.getGreen()) * w),
(int) (c1.getBlue() + (c2.getBlue() - c1.getBlue()) * w),
(int) (c1.getAlpha() + (c2.getAlpha() - c1.getAlpha()) * w)
);
}
public static int mixColors(int color1, int color2, float w) {
int a1 = (color1 >> 24);
int r1 = (color1 >> 16) & 0xFF;
int g1 = (color1 >> 8) & 0xFF;
int b1 = color1 & 0xFF;
int a2 = (color2 >> 24);
int r2 = (color2 >> 16) & 0xFF;
int g2 = (color2 >> 8) & 0xFF;
int b2 = color2 & 0xFF;
return
((int) (a1 + (a2 - a1) * w) << 24) +
((int) (r1 + (r2 - r1) * w) << 16) +
((int) (g1 + (g2 - g1) * w) << 8) +
((int) (b1 + (b2 - b1) * w) << 0);
}
public static Color rainbowColor(int timeStep) {
int localTimeStep = Math.abs(timeStep) % 1536;
int timeStepInPhase = localTimeStep % 256;
int phaseBlue = localTimeStep / 256;
int red = colorInPhase(phaseBlue + 4, timeStepInPhase);
int green = colorInPhase(phaseBlue + 2, timeStepInPhase);
int blue = colorInPhase(phaseBlue, timeStepInPhase);
return new Color(red, green, blue);
}
private static int colorInPhase(int phase, int progress) {
phase = phase % 6;
if (phase <= 1)
return 0;
if (phase == 2)
return progress;
if (phase <= 4)
return 255;
else
return 255 - progress;
}
public static Color generateFromLong(long l) {
return rainbowColor(Hashing.crc32().hashLong(l).asInt())
.mixWith(WHITE, 0.5f);
}
}

View file

@ -0,0 +1,103 @@
package com.jozufozu.flywheel.util;
import java.nio.ByteBuffer;
import org.lwjgl.system.MemoryUtil;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.datafixers.util.Pair;
public class UnsafeBlockFormatReader implements ModelReader {
private final int vertexCount;
private final int stride;
private final long base;
public UnsafeBlockFormatReader(BufferBuilder builder) {
VertexFormat vertexFormat = builder.getVertexFormat();
Pair<BufferBuilder.DrawState, ByteBuffer> data = builder.popNextBuffer();
this.base = MemoryUtil.memAddress(data.getSecond());
this.vertexCount = data.getFirst().vertexCount();
this.stride = vertexFormat.getVertexSize();
}
private long ptr(long index) {
return base + index * stride;
}
@Override
public boolean isEmpty() {
return vertexCount == 0;
}
@Override
public float getX(int index) {
return MemoryUtil.memGetFloat(ptr(index));
}
@Override
public float getY(int index) {
return MemoryUtil.memGetFloat(ptr(index) + 4);
}
@Override
public float getZ(int index) {
return MemoryUtil.memGetFloat(ptr(index) + 8);
}
@Override
public byte getR(int index) {
return MemoryUtil.memGetByte(ptr(index) + 12);
}
@Override
public byte getG(int index) {
return MemoryUtil.memGetByte(ptr(index) + 13);
}
@Override
public byte getB(int index) {
return MemoryUtil.memGetByte(ptr(index) + 14);
}
@Override
public byte getA(int index) {
return MemoryUtil.memGetByte(ptr(index) + 15);
}
@Override
public float getU(int index) {
return MemoryUtil.memGetFloat(ptr(index) + 16);
}
@Override
public float getV(int index) {
return MemoryUtil.memGetFloat(ptr(index) + 20);
}
@Override
public int getLight(int index) {
return MemoryUtil.memGetInt(ptr(index) + 24);
}
@Override
public float getNX(int index) {
return RenderMath.f(MemoryUtil.memGetByte(ptr(index) + 28));
}
@Override
public float getNY(int index) {
return RenderMath.f(MemoryUtil.memGetByte(ptr(index) + 29));
}
@Override
public float getNZ(int index) {
return RenderMath.f(MemoryUtil.memGetByte(ptr(index) + 30));
}
@Override
public int getVertexCount() {
return vertexCount;
}
}

View file

@ -1,5 +1,5 @@
struct Vertex {
vec3 pos;
vec3 normal;
vec2 texCoords;
vec3 normal;
};