Add client rendering and collision optimizations with parity tests
This commit is contained in:
parent
28494403f2
commit
5c04905561
45 changed files with 3167 additions and 0 deletions
7
client/src/main/java/dev/quorate/client/ClientMod.java
Normal file
7
client/src/main/java/dev/quorate/client/ClientMod.java
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package dev.quorate.client;
|
||||
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
|
||||
@Mod(value = "saltatory_client", dist = Dist.CLIENT)
|
||||
public final class ClientMod {}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package dev.quorate.client.mixin;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
|
||||
|
||||
import dev.quorate.client.render.WorkQueue;
|
||||
import dev.quorate.client.render.WorkTarget;
|
||||
|
||||
import net.irisshaders.iris.vertices.MojangBufferAccessor;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(ByteBufferBuilder.class)
|
||||
public abstract class BufferTasks implements WorkTarget {
|
||||
@Unique private WorkQueue work;
|
||||
|
||||
@Override
|
||||
public WorkQueue workQueue() {
|
||||
if (work == null) work = new WorkQueue();
|
||||
return work;
|
||||
}
|
||||
|
||||
@Inject(method = "build", at = @At("HEAD"))
|
||||
private void publish(CallbackInfoReturnable<ByteBufferBuilder.Result> info) {
|
||||
if (work != null) work.drain(((MojangBufferAccessor) this).getPointer(), true);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = {"clear", "discard", "freeResult"},
|
||||
at = @At("HEAD"))
|
||||
private void compact(CallbackInfo info) {
|
||||
if (work != null) work.drain(((MojangBufferAccessor) this).getPointer(), true);
|
||||
}
|
||||
|
||||
@WrapMethod(method = "close")
|
||||
private void release(Operation<Void> original) {
|
||||
try {
|
||||
if (work != null) work.drain(0, false);
|
||||
} finally {
|
||||
original.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package dev.quorate.client.mixin;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import dev.quorate.client.render.ColorDraw;
|
||||
import dev.quorate.client.render.TangentSkip;
|
||||
|
||||
import net.createmod.catnip.render.ShadeSeparatingSuperByteBuffer;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(value = ShadeSeparatingSuperByteBuffer.class, remap = false)
|
||||
public abstract class CreateAttrs {
|
||||
@WrapMethod(method = "renderInto")
|
||||
private void render(PoseStack pose, VertexConsumer consumer, Operation<Void> original) {
|
||||
if (TangentSkip.enabled()) {
|
||||
TangentSkip.scoped(() -> emit(pose, consumer, original));
|
||||
} else original.call(pose, consumer);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void emit(PoseStack pose, VertexConsumer consumer, Operation<Void> original) {
|
||||
if (!ColorDraw.render((ShadeSeparatingSuperByteBuffer) (Object) this, pose, consumer))
|
||||
original.call(pose, consumer);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package dev.quorate.client.mixin;
|
||||
|
||||
import net.minecraft.core.Cursor3D;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(value = Cursor3D.class, remap = false)
|
||||
public abstract class CursorStep {
|
||||
@Shadow @Final private int width;
|
||||
@Shadow @Final private int height;
|
||||
@Shadow @Final private int end;
|
||||
@Shadow private int index;
|
||||
@Shadow private int x;
|
||||
@Shadow private int y;
|
||||
@Shadow private int z;
|
||||
|
||||
@Overwrite
|
||||
public boolean advance() {
|
||||
if (index == end) return false;
|
||||
if (width > 0 && height > 0 && end > 0) {
|
||||
if (index == 0) {
|
||||
x = 0;
|
||||
y = 0;
|
||||
z = 0;
|
||||
} else if (++x == width) {
|
||||
x = 0;
|
||||
if (++y == height) {
|
||||
y = 0;
|
||||
z++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
x = index % width;
|
||||
int row = index / width;
|
||||
y = row % height;
|
||||
z = row / height;
|
||||
}
|
||||
index++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
37
client/src/main/java/dev/quorate/client/mixin/MeshCull.java
Normal file
37
client/src/main/java/dev/quorate/client/mixin/MeshCull.java
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package dev.quorate.client.mixin;
|
||||
|
||||
import dev.quorate.client.render.MeshPlan;
|
||||
import dev.quorate.client.render.PlaneBound;
|
||||
import dev.quorate.client.render.PlaneTarget;
|
||||
|
||||
import net.createmod.catnip.render.TemplateMesh;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(value = TemplateMesh.class, remap = false)
|
||||
public abstract class MeshCull implements PlaneTarget {
|
||||
@Shadow private int[] data;
|
||||
@Unique private MeshPlan plan;
|
||||
|
||||
@Unique
|
||||
private MeshPlan current() {
|
||||
var value = plan;
|
||||
if (value == null || !value.matches(data)) {
|
||||
value = new MeshPlan(data);
|
||||
plan = value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaneBound.Model plane() {
|
||||
return current().model();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateMesh snapshot() {
|
||||
return current().snapshot();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package dev.quorate.client.mixin;
|
||||
|
||||
import net.irisshaders.iris.layer.OuterWrappedRenderType;
|
||||
import net.minecraft.client.renderer.RenderStateShard;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(value = OuterWrappedRenderType.class, remap = false)
|
||||
public interface OuterState {
|
||||
@Accessor("extra")
|
||||
RenderStateShard extra();
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package dev.quorate.client.mixin;
|
||||
|
||||
import it.unimi.dsi.fastutil.doubles.DoubleList;
|
||||
|
||||
import net.minecraft.core.Direction.Axis;
|
||||
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(value = VoxelShape.class, remap = false)
|
||||
public interface ShapeAccess {
|
||||
@Accessor("shape")
|
||||
DiscreteVoxelShape grid();
|
||||
|
||||
@Invoker("getCoords")
|
||||
DoubleList coords(Axis axis);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package dev.quorate.client.mixin;
|
||||
|
||||
import dev.quorate.client.shape.BoxJoin;
|
||||
|
||||
import net.minecraft.world.phys.shapes.BooleanOp;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(value = Shapes.class, remap = false)
|
||||
public abstract class ShapeMasks {
|
||||
@Inject(
|
||||
method =
|
||||
"joinIsNotEmpty(Lnet/minecraft/world/phys/shapes/VoxelShape;Lnet/minecraft/world/phys/shapes/VoxelShape;Lnet/minecraft/world/phys/shapes/BooleanOp;)Z",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true)
|
||||
private static void intersect(
|
||||
VoxelShape first,
|
||||
VoxelShape second,
|
||||
BooleanOp operation,
|
||||
CallbackInfoReturnable<Boolean> result) {
|
||||
if (operation == BooleanOp.AND) {
|
||||
var answer = BoxJoin.test(first, second);
|
||||
if (answer != BoxJoin.Match.UNKNOWN) result.setReturnValue(answer == BoxJoin.Match.YES);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
client/src/main/java/dev/quorate/client/mixin/TangentAttrs.java
Normal file
100
client/src/main/java/dev/quorate/client/mixin/TangentAttrs.java
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package dev.quorate.client.mixin;
|
||||
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
|
||||
import dev.quorate.client.render.FaceCull;
|
||||
import dev.quorate.client.render.FaceRead;
|
||||
import dev.quorate.client.render.TangentSkip;
|
||||
import dev.quorate.client.render.TangentTarget;
|
||||
|
||||
import net.irisshaders.iris.layer.BlockEntityRenderStateShard;
|
||||
import net.irisshaders.iris.layer.OuterWrappedRenderType;
|
||||
import net.irisshaders.iris.vertices.IrisVertexFormats;
|
||||
import net.irisshaders.iris.vertices.MojangBufferAccessor;
|
||||
import net.irisshaders.iris.vertices.NormalHelper;
|
||||
import net.irisshaders.iris.vertices.views.QuadView;
|
||||
import net.irisshaders.iris.vertices.views.TriView;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
// INVOKE injection into Iris's priority-1000 merged helper requires a higher priority.
|
||||
@Mixin(value = BufferBuilder.class, priority = 1001, remap = false)
|
||||
public abstract class TangentAttrs implements TangentTarget {
|
||||
@Unique private boolean blockLayer;
|
||||
@Unique private boolean backCull;
|
||||
|
||||
@Override
|
||||
public void tangentType(RenderType type) {
|
||||
backCull = FaceCull.culls(type);
|
||||
blockLayer =
|
||||
type instanceof OuterWrappedRenderType outer
|
||||
&& ((OuterState) outer).extra() == BlockEntityRenderStateShard.INSTANCE
|
||||
&& RenderType.chunkBufferLayers().contains(outer.unwrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cullsBackfaces() {
|
||||
return backCull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean skipsTangents() {
|
||||
return TangentSkip.active() && eligible();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean eligible() {
|
||||
return format == IrisVertexFormats.ENTITY
|
||||
|| format == IrisVertexFormats.TERRAIN && blockLayer;
|
||||
}
|
||||
|
||||
@Shadow @Final private VertexFormat format;
|
||||
@Shadow @Final private ByteBufferBuilder buffer;
|
||||
@Shadow @Final private long[] vertexOffsets;
|
||||
|
||||
@Redirect(
|
||||
method = "fillExtendedData",
|
||||
at =
|
||||
@At(
|
||||
value = "INVOKE",
|
||||
target =
|
||||
"Lnet/irisshaders/iris/vertices/NormalHelper;computeFaceNormal(Lorg/joml/Vector3f;Lnet/irisshaders/iris/vertices/views/QuadView;)V"))
|
||||
private void normal(Vector3f output, QuadView view) {
|
||||
FaceRead.compute(output, ((MojangBufferAccessor) buffer).getPointer(), vertexOffsets);
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "fillExtendedData",
|
||||
at =
|
||||
@At(
|
||||
value = "INVOKE",
|
||||
target =
|
||||
"Lnet/irisshaders/iris/vertices/NormalHelper;computeTangent(FFFLnet/irisshaders/iris/vertices/views/TriView;)I"))
|
||||
private int quad(float x, float y, float z, TriView view) {
|
||||
return eligible()
|
||||
? TangentSkip.quad(x, y, z, view)
|
||||
: NormalHelper.computeTangent(x, y, z, view);
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "fillExtendedData",
|
||||
at =
|
||||
@At(
|
||||
value = "INVOKE",
|
||||
target =
|
||||
"Lnet/irisshaders/iris/vertices/NormalHelper;computeTangentSmooth(FFFLnet/irisshaders/iris/vertices/views/TriView;)I"))
|
||||
private int smooth(float x, float y, float z, TriView view) {
|
||||
return eligible()
|
||||
? TangentSkip.smooth(x, y, z, view)
|
||||
: NormalHelper.computeTangentSmooth(x, y, z, view);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package dev.quorate.client.mixin;
|
||||
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import dev.quorate.client.render.TangentTarget;
|
||||
|
||||
import net.irisshaders.batchedentityrendering.impl.SegmentedBufferBuilder;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(
|
||||
value = {MultiBufferSource.BufferSource.class, SegmentedBufferBuilder.class},
|
||||
remap = false)
|
||||
public abstract class TangentSource {
|
||||
@Inject(method = "getBuffer", at = @At("RETURN"))
|
||||
private void identify(RenderType type, CallbackInfoReturnable<VertexConsumer> result) {
|
||||
if (result.getReturnValue() instanceof TangentTarget target) target.tangentType(type);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package dev.quorate.client.render;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public final class BatchData {
|
||||
private static final boolean LITTLE = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
|
||||
|
||||
private BatchData() {}
|
||||
|
||||
private static byte normal(float value) {
|
||||
return (byte) ((int) (Mth.clamp(value, -1f, 1f) * 127f) & 255);
|
||||
}
|
||||
|
||||
private static void uv(long pointer, int value) {
|
||||
if (LITTLE) MemoryUtil.memPutInt(pointer, value);
|
||||
else {
|
||||
MemoryUtil.memPutShort(pointer, (short) value);
|
||||
MemoryUtil.memPutShort(pointer + 2, (short) (value >>> 16));
|
||||
}
|
||||
}
|
||||
|
||||
public static void write(
|
||||
long pointer,
|
||||
boolean entity,
|
||||
float x,
|
||||
float y,
|
||||
float z,
|
||||
int abgr,
|
||||
float u,
|
||||
float v,
|
||||
int overlay,
|
||||
int light,
|
||||
float nx,
|
||||
float ny,
|
||||
float nz) {
|
||||
attributes(pointer, entity, x, y, z, abgr, u, v, overlay, light);
|
||||
int offset = entity ? 28 : 24;
|
||||
MemoryUtil.memPutByte(pointer + offset + 4, normal(nx));
|
||||
MemoryUtil.memPutByte(pointer + offset + 5, normal(ny));
|
||||
MemoryUtil.memPutByte(pointer + offset + 6, normal(nz));
|
||||
}
|
||||
|
||||
public static void attributes(
|
||||
long pointer,
|
||||
boolean entity,
|
||||
float x,
|
||||
float y,
|
||||
float z,
|
||||
int abgr,
|
||||
float u,
|
||||
float v,
|
||||
int overlay,
|
||||
int light) {
|
||||
MemoryUtil.memPutFloat(pointer, x);
|
||||
MemoryUtil.memPutFloat(pointer + 4, y);
|
||||
MemoryUtil.memPutFloat(pointer + 8, z);
|
||||
MemoryUtil.memPutInt(pointer + 12, LITTLE ? abgr : Integer.reverseBytes(abgr));
|
||||
MemoryUtil.memPutFloat(pointer + 16, u);
|
||||
MemoryUtil.memPutFloat(pointer + 20, v);
|
||||
if (entity) uv(pointer + 24, overlay);
|
||||
int offset = entity ? 28 : 24;
|
||||
uv(pointer + offset, light);
|
||||
}
|
||||
}
|
||||
322
client/src/main/java/dev/quorate/client/render/BatchDraw.java
Normal file
322
client/src/main/java/dev/quorate/client/render/BatchDraw.java
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
package dev.quorate.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormatElement;
|
||||
|
||||
import net.createmod.catnip.render.SuperByteBuffer;
|
||||
import net.createmod.catnip.render.TemplateMesh;
|
||||
import net.irisshaders.iris.shadows.ShadowRenderer;
|
||||
import net.irisshaders.iris.uniforms.CapturedRenderingState;
|
||||
import net.irisshaders.iris.vertices.ExtendedDataHelper;
|
||||
import net.irisshaders.iris.vertices.ImmediateState;
|
||||
import net.irisshaders.iris.vertices.IrisVertexFormats;
|
||||
import net.irisshaders.iris.vertices.MojangBufferAccessor;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
final class BatchDraw {
|
||||
private record Layout(
|
||||
boolean entity, int stride, int idOffset, int midOffset, QuadEnd.Format quad) {}
|
||||
|
||||
private static final class State {
|
||||
private static final VarHandle BUILDING = field("building", boolean.class);
|
||||
private static final VarHandle BUFFER = field("buffer", ByteBufferBuilder.class);
|
||||
private static final VarHandle FORMAT = field("format", VertexFormat.class);
|
||||
private static final VarHandle MODE = field("mode", VertexFormat.Mode.class);
|
||||
private static final VarHandle POINTER = field("vertexPointer", long.class);
|
||||
private static final VarHandle VERTICES = field("vertices", int.class);
|
||||
private static final VarHandle ELEMENTS = field("elementsToFill", int.class);
|
||||
private static final VarHandle COUNT = field("iris$vertexCount", int.class);
|
||||
private static final VarHandle OFFSETS = field("vertexOffsets", long[].class);
|
||||
private static final VarHandle SKIP = field("skipEndVertexOnce", boolean.class);
|
||||
private static final VarHandle EXTENDING = field("extending", boolean.class);
|
||||
private static final VarHandle X = field("currentLocalPosX", int.class);
|
||||
private static final VarHandle Y = field("currentLocalPosY", int.class);
|
||||
private static final VarHandle Z = field("currentLocalPosZ", int.class);
|
||||
private static final VarHandle BLOCK = field("currentBlock", int.class);
|
||||
private static final VarHandle TYPE = field("currentRenderType", byte.class);
|
||||
private static final VarHandle NORMAL = field("normal", Vector3f.class);
|
||||
private static final MethodHandle FILL;
|
||||
private static final Layout ENTITY = layout(IrisVertexFormats.ENTITY, true);
|
||||
private static final Layout TERRAIN = layout(IrisVertexFormats.TERRAIN, false);
|
||||
private static final int BASE_MASK =
|
||||
VertexFormatElement.COLOR.mask()
|
||||
| VertexFormatElement.UV0.mask()
|
||||
| VertexFormatElement.UV1.mask()
|
||||
| VertexFormatElement.UV2.mask()
|
||||
| VertexFormatElement.NORMAL.mask();
|
||||
|
||||
static {
|
||||
try {
|
||||
FILL =
|
||||
MethodHandles.privateLookupIn(BufferBuilder.class, MethodHandles.lookup())
|
||||
.findVirtual(
|
||||
BufferBuilder.class,
|
||||
"fillExtendedData",
|
||||
MethodType.methodType(void.class, int.class));
|
||||
} catch (ReflectiveOperationException failure) {
|
||||
throw new ExceptionInInitializerError(failure);
|
||||
}
|
||||
}
|
||||
|
||||
private static VarHandle field(String name, Class<?> type) {
|
||||
try {
|
||||
return MethodHandles.privateLookupIn(BufferBuilder.class, MethodHandles.lookup())
|
||||
.findVarHandle(BufferBuilder.class, name, type);
|
||||
} catch (ReflectiveOperationException failure) {
|
||||
throw new ExceptionInInitializerError(failure);
|
||||
}
|
||||
}
|
||||
|
||||
private static Layout layout(VertexFormat format, boolean entity) {
|
||||
return new Layout(
|
||||
entity,
|
||||
format.getVertexSize(),
|
||||
format.getOffset(
|
||||
entity
|
||||
? IrisVertexFormats.ENTITY_ID_ELEMENT
|
||||
: IrisVertexFormats.ENTITY_ELEMENT),
|
||||
entity ? -1 : format.getOffset(IrisVertexFormats.MID_BLOCK_ELEMENT),
|
||||
new QuadEnd.Format(
|
||||
format.getOffset(IrisVertexFormats.MID_TEXTURE_ELEMENT),
|
||||
format.getOffset(VertexFormatElement.NORMAL),
|
||||
format.getOffset(IrisVertexFormats.TANGENT_ELEMENT)));
|
||||
}
|
||||
}
|
||||
|
||||
private final BufferBuilder target;
|
||||
private final ByteBufferBuilder storage;
|
||||
private final Layout layout;
|
||||
private final long[] offsets;
|
||||
private final boolean completion;
|
||||
private final boolean omitMid;
|
||||
private final boolean strip;
|
||||
private final Vector3f face;
|
||||
|
||||
private BatchDraw(BufferBuilder target) {
|
||||
this.target = target;
|
||||
storage = (ByteBufferBuilder) State.BUFFER.get(target);
|
||||
layout =
|
||||
State.FORMAT.get(target) == IrisVertexFormats.ENTITY ? State.ENTITY : State.TERRAIN;
|
||||
offsets = (long[]) State.OFFSETS.get(target);
|
||||
completion = ((TangentTarget) target).skipsTangents();
|
||||
omitMid =
|
||||
completion
|
||||
&& !layout.entity()
|
||||
&& ShaderInputs.current().skipMidBlock()
|
||||
&& storage.getClass() == ByteBufferBuilder.class;
|
||||
strip = omitMid && ShaderInputs.current().skipMovingFields() && !ShadowRenderer.ACTIVE;
|
||||
face = completion ? (Vector3f) State.NORMAL.get(target) : null;
|
||||
}
|
||||
|
||||
public static BatchDraw create(BufferBuilder target, int count) {
|
||||
if (count < 8) return null;
|
||||
var format = State.FORMAT.get(target);
|
||||
if (State.MODE.get(target) != VertexFormat.Mode.QUADS
|
||||
|| !(boolean) State.EXTENDING.get(target)
|
||||
|| format != IrisVertexFormats.ENTITY && format != IrisVertexFormats.TERRAIN)
|
||||
return null;
|
||||
return new BatchDraw(target);
|
||||
}
|
||||
|
||||
private static void endPrevious(
|
||||
BufferBuilder target,
|
||||
ByteBufferBuilder storage,
|
||||
long[] offsets,
|
||||
long pointer,
|
||||
QuadEnd.Format quad,
|
||||
Vector3f face,
|
||||
boolean completion,
|
||||
boolean strip) {
|
||||
State.ELEMENTS.set(target, 0);
|
||||
if ((boolean) State.SKIP.get(target)) {
|
||||
State.SKIP.set(target, false);
|
||||
return;
|
||||
}
|
||||
int count = (int) State.COUNT.get(target);
|
||||
long base = ((MojangBufferAccessor) storage).getPointer();
|
||||
offsets[count] = pointer - base;
|
||||
State.COUNT.set(target, count + 1);
|
||||
if (count + 1 == 4) {
|
||||
try {
|
||||
if (completion) {
|
||||
State.COUNT.set(target, 0);
|
||||
if (strip)
|
||||
QuadEnd.normals(base, offsets, face, quad, ImmediateState.isRenderingLevel);
|
||||
else QuadEnd.write(base, offsets, face, quad, ImmediateState.isRenderingLevel);
|
||||
} else {
|
||||
State.FILL.invokeExact(target, 4);
|
||||
}
|
||||
} catch (Throwable failure) {
|
||||
throw new AssertionError(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void metadata(
|
||||
BufferBuilder target,
|
||||
long pointer,
|
||||
boolean entity,
|
||||
int idOffset,
|
||||
int midOffset,
|
||||
float x,
|
||||
float y,
|
||||
float z,
|
||||
boolean omitMid) {
|
||||
long id = pointer + idOffset;
|
||||
if (entity) {
|
||||
var state = CapturedRenderingState.INSTANCE;
|
||||
MemoryUtil.memPutShort(id, (short) state.getCurrentRenderedEntity());
|
||||
MemoryUtil.memPutShort(id + 2, (short) state.getCurrentRenderedBlockEntity());
|
||||
MemoryUtil.memPutShort(id + 4, (short) state.getCurrentRenderedItem());
|
||||
} else {
|
||||
MemoryUtil.memPutShort(id, (short) (int) State.BLOCK.get(target));
|
||||
MemoryUtil.memPutShort(id + 2, (byte) State.TYPE.get(target));
|
||||
if (!omitMid) {
|
||||
MemoryUtil.memPutInt(
|
||||
pointer + midOffset,
|
||||
ExtendedDataHelper.computeMidBlock(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
(int) State.X.get(target),
|
||||
(int) State.Y.get(target),
|
||||
(int) State.Z.get(target)));
|
||||
MemoryUtil.memPutByte(pointer + midOffset + 3, (byte) -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean stages(TemplateMesh template, int outputCount, boolean discard) {
|
||||
return strip
|
||||
&& discard
|
||||
&& BatchWork.supports(template, outputCount, (int) State.VERTICES.get(target));
|
||||
}
|
||||
|
||||
public void emit(
|
||||
TemplateMesh template,
|
||||
Matrix4f matrix,
|
||||
Matrix3f normalMat,
|
||||
Vector4f position,
|
||||
Vector3f normal,
|
||||
boolean customOverlay,
|
||||
int overlay,
|
||||
boolean customLight,
|
||||
int light,
|
||||
int count) {
|
||||
var builder = target;
|
||||
var buffer = storage;
|
||||
var slots = offsets;
|
||||
var quad = layout.quad();
|
||||
var face = this.face;
|
||||
boolean completion = this.completion;
|
||||
boolean entity = layout.entity();
|
||||
int stride = layout.stride();
|
||||
int idOffset = layout.idOffset();
|
||||
int midOffset = layout.midOffset();
|
||||
long pointer = 0;
|
||||
int tail = 0;
|
||||
boolean discard =
|
||||
completion
|
||||
&& ImmediateState.isRenderingLevel
|
||||
&& buffer.getClass() == ByteBufferBuilder.class;
|
||||
var selected =
|
||||
strip && ((TangentTarget) builder).cullsBackfaces()
|
||||
? FaceCull.plan(template, matrix, count, (int) State.VERTICES.get(builder))
|
||||
: null;
|
||||
int outputCount = selected == null ? count : selected.count() * 4;
|
||||
boolean parallel = stages(template, outputCount, discard);
|
||||
boolean tailStart = false;
|
||||
for (int output = 0; output < outputCount; output++) {
|
||||
if (parallel && output == 4 && ImmediateState.isRenderingLevel) {
|
||||
endPrevious(builder, buffer, slots, pointer, quad, face, completion, strip);
|
||||
int middle = outputCount - 8;
|
||||
long block = buffer.reserve((middle + 1) * stride);
|
||||
pointer = block + (long) middle * stride;
|
||||
State.VERTICES.set(builder, (int) State.VERTICES.get(builder) + middle + 1);
|
||||
State.POINTER.set(builder, pointer);
|
||||
var work =
|
||||
new BatchWork(
|
||||
((PlaneTarget) template).snapshot(),
|
||||
matrix,
|
||||
selected,
|
||||
outputCount,
|
||||
stride,
|
||||
quad,
|
||||
customLight,
|
||||
light);
|
||||
((WorkTarget) buffer)
|
||||
.workQueue()
|
||||
.submit(block - ((MojangBufferAccessor) buffer).getPointer(), work);
|
||||
output = outputCount - 4;
|
||||
tailStart = true;
|
||||
}
|
||||
int i = selected == null ? output : selected.starts()[output / 4] + output % 4;
|
||||
position.set(template.x(i), template.y(i), template.z(i), 1f).mul(matrix);
|
||||
// The final quad can be completed after the render state changes.
|
||||
boolean keep = !discard || i >= count - 4;
|
||||
if (keep) {
|
||||
int packedNormal = template.normal(i);
|
||||
float nx = (byte) (packedNormal & 255) / 127f;
|
||||
float ny = (byte) (packedNormal >>> 8 & 255) / 127f;
|
||||
float nz = (byte) (packedNormal >>> 16 & 255) / 127f;
|
||||
normal.set(nx, ny, nz).mul(normalMat);
|
||||
}
|
||||
int color = template.color(i);
|
||||
float u = template.u(i);
|
||||
float v = template.v(i);
|
||||
int vertexOverlay = customOverlay ? overlay : template.overlay(i);
|
||||
int vertexLight = template.light(i);
|
||||
if (customLight) vertexLight = SuperByteBuffer.maxLight(vertexLight, light);
|
||||
float x = position.x;
|
||||
float y = position.y;
|
||||
float z = position.z;
|
||||
if (i == 0) {
|
||||
builder.addVertex(x, y, z);
|
||||
pointer = (long) State.POINTER.get(builder);
|
||||
tail = (int) State.ELEMENTS.get(builder) & ~State.BASE_MASK;
|
||||
} else if (tailStart) {
|
||||
tailStart = false;
|
||||
} else {
|
||||
if (!(boolean) State.BUILDING.get(builder))
|
||||
throw new IllegalStateException("Not building!");
|
||||
endPrevious(builder, buffer, slots, pointer, quad, face, completion, strip);
|
||||
State.VERTICES.set(builder, (int) State.VERTICES.get(builder) + 1);
|
||||
pointer = buffer.reserve(stride);
|
||||
State.POINTER.set(builder, pointer);
|
||||
if (!strip)
|
||||
metadata(builder, pointer, entity, idOffset, midOffset, x, y, z, omitMid);
|
||||
}
|
||||
if (keep)
|
||||
BatchData.write(
|
||||
pointer,
|
||||
entity,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
color,
|
||||
u,
|
||||
v,
|
||||
vertexOverlay,
|
||||
vertexLight,
|
||||
normal.x,
|
||||
normal.y,
|
||||
normal.z);
|
||||
else
|
||||
BatchData.attributes(
|
||||
pointer, entity, x, y, z, color, u, v, vertexOverlay, vertexLight);
|
||||
State.ELEMENTS.set(builder, tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package dev.quorate.client.render;
|
||||
|
||||
import net.createmod.catnip.render.SuperByteBuffer;
|
||||
import net.createmod.catnip.render.TemplateMesh;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
public final class BatchWork {
|
||||
private final TemplateMesh mesh;
|
||||
private final Matrix4f matrix;
|
||||
private final int[] starts;
|
||||
private final int quads;
|
||||
private final int stride;
|
||||
private final QuadEnd.Format format;
|
||||
private final boolean customLight;
|
||||
private final int light;
|
||||
|
||||
BatchWork(
|
||||
TemplateMesh mesh,
|
||||
Matrix4f matrix,
|
||||
PlaneBound.Selection selected,
|
||||
int count,
|
||||
int stride,
|
||||
QuadEnd.Format format,
|
||||
boolean customLight,
|
||||
int light) {
|
||||
this.mesh = mesh;
|
||||
this.matrix = new Matrix4f(matrix);
|
||||
starts = selected == null ? null : selected.starts();
|
||||
quads = count / 4 - 2;
|
||||
this.stride = stride;
|
||||
this.format = format;
|
||||
this.customLight = customLight;
|
||||
this.light = light;
|
||||
}
|
||||
|
||||
static boolean supports(TemplateMesh template, int count, int prefix) {
|
||||
return template.getClass() == TemplateMesh.class
|
||||
&& prefix % 4 == 0
|
||||
&& count > 8
|
||||
&& count % 4 == 0;
|
||||
}
|
||||
|
||||
int bytes() {
|
||||
return quads * 4 * stride;
|
||||
}
|
||||
|
||||
void write(long target) {
|
||||
var position = new Vector4f();
|
||||
var face = new Vector3f();
|
||||
long[] offsets = {0, stride, 2L * stride, 3L * stride};
|
||||
for (int quad = 0; quad < quads; quad++) {
|
||||
int source = starts == null ? (quad + 1) * 4 : starts[quad + 1];
|
||||
long pointer = target + (long) quad * 4 * stride;
|
||||
for (int vertex = 0; vertex < 4; vertex++) {
|
||||
int i = source + vertex;
|
||||
position.set(mesh.x(i), mesh.y(i), mesh.z(i), 1f).mul(matrix);
|
||||
int vertexLight = mesh.light(i);
|
||||
if (customLight) vertexLight = SuperByteBuffer.maxLight(vertexLight, light);
|
||||
BatchData.attributes(
|
||||
pointer + (long) vertex * stride,
|
||||
false,
|
||||
position.x,
|
||||
position.y,
|
||||
position.z,
|
||||
mesh.color(i),
|
||||
mesh.u(i),
|
||||
mesh.v(i),
|
||||
0,
|
||||
vertexLight);
|
||||
}
|
||||
for (int vertex = 0; vertex < 4; vertex++) offsets[vertex] = (long) vertex * stride;
|
||||
QuadEnd.normals(pointer, offsets, face, format, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
139
client/src/main/java/dev/quorate/client/render/ColorDraw.java
Normal file
139
client/src/main/java/dev/quorate/client/render/ColorDraw.java
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package dev.quorate.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import net.createmod.catnip.render.ShadeSeparatingSuperByteBuffer;
|
||||
import net.createmod.catnip.render.SuperByteBuffer;
|
||||
import net.createmod.catnip.render.TemplateMesh;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
public final class ColorDraw {
|
||||
private static final VarHandle TEMPLATE = field("template", TemplateMesh.class);
|
||||
private static final VarHandle R = field("r", float.class);
|
||||
private static final VarHandle G = field("g", float.class);
|
||||
private static final VarHandle B = field("b", float.class);
|
||||
private static final VarHandle A = field("a", float.class);
|
||||
private static final VarHandle SHIFT =
|
||||
field("spriteShiftFunc", SuperByteBuffer.SpriteShiftFunc.class);
|
||||
private static final VarHandle LEVEL = field("useLevelLight", boolean.class);
|
||||
private static final VarHandle HAS_OVERLAY = field("hasCustomOverlay", boolean.class);
|
||||
private static final VarHandle OVERLAY = field("overlay", int.class);
|
||||
private static final VarHandle HAS_LIGHT = field("hasCustomLight", boolean.class);
|
||||
private static final VarHandle LIGHT = field("packedLight", int.class);
|
||||
private static final VarHandle MODEL = field("modelMat", Matrix4f.class);
|
||||
private static final VarHandle NORMAL_MAT = field("normalMat", Matrix3f.class);
|
||||
private static final VarHandle POSITION = field("pos", Vector4f.class);
|
||||
private static final VarHandle NORMAL = field("normal", Vector3f.class);
|
||||
|
||||
private ColorDraw() {}
|
||||
|
||||
private static VarHandle field(String name, Class<?> type) {
|
||||
try {
|
||||
return MethodHandles.privateLookupIn(
|
||||
ShadeSeparatingSuperByteBuffer.class, MethodHandles.lookup())
|
||||
.findVarHandle(ShadeSeparatingSuperByteBuffer.class, name, type);
|
||||
} catch (ReflectiveOperationException failure) {
|
||||
throw new ExceptionInInitializerError(failure);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean render(
|
||||
ShadeSeparatingSuperByteBuffer source, PoseStack pose, VertexConsumer consumer) {
|
||||
if (source.getClass() != ShadeSeparatingSuperByteBuffer.class
|
||||
|| consumer.getClass() != BufferBuilder.class
|
||||
|| TEMPLATE.get(source).getClass() != TemplateMesh.class
|
||||
|| (float) R.get(source) != 1f
|
||||
|| (float) G.get(source) != 1f
|
||||
|| (float) B.get(source) != 1f
|
||||
|| (float) A.get(source) != 1f
|
||||
|| SHIFT.get(source) != null
|
||||
|| (boolean) LEVEL.get(source)) return false;
|
||||
if (source.isEmpty()) return true;
|
||||
var template = (TemplateMesh) TEMPLATE.get(source);
|
||||
var transforms = source.getTransforms().last();
|
||||
var matrix = ((Matrix4f) MODEL.get(source)).set(pose.last().pose()).mul(transforms.pose());
|
||||
var normalMat =
|
||||
((Matrix3f) NORMAL_MAT.get(source))
|
||||
.set(pose.last().normal())
|
||||
.mul(transforms.normal());
|
||||
var position = (Vector4f) POSITION.get(source);
|
||||
var normal = (Vector3f) NORMAL.get(source);
|
||||
boolean customOverlay = (boolean) HAS_OVERLAY.get(source);
|
||||
int overlay = (int) OVERLAY.get(source);
|
||||
boolean customLight = (boolean) HAS_LIGHT.get(source);
|
||||
int light = (int) LIGHT.get(source);
|
||||
int count = template.vertexCount();
|
||||
var batch = BatchDraw.create((BufferBuilder) consumer, count);
|
||||
if (batch == null) {
|
||||
emit(
|
||||
template,
|
||||
matrix,
|
||||
normalMat,
|
||||
position,
|
||||
normal,
|
||||
customOverlay,
|
||||
overlay,
|
||||
customLight,
|
||||
light,
|
||||
consumer,
|
||||
count);
|
||||
} else {
|
||||
batch.emit(
|
||||
template,
|
||||
matrix,
|
||||
normalMat,
|
||||
position,
|
||||
normal,
|
||||
customOverlay,
|
||||
overlay,
|
||||
customLight,
|
||||
light,
|
||||
count);
|
||||
}
|
||||
source.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void emit(
|
||||
TemplateMesh template,
|
||||
Matrix4f matrix,
|
||||
Matrix3f normalMat,
|
||||
Vector4f position,
|
||||
Vector3f normal,
|
||||
boolean customOverlay,
|
||||
int overlay,
|
||||
boolean customLight,
|
||||
int light,
|
||||
VertexConsumer consumer,
|
||||
int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
position.set(template.x(i), template.y(i), template.z(i), 1f).mul(matrix);
|
||||
int packedNormal = template.normal(i);
|
||||
float nx = (byte) (packedNormal & 255) / 127f;
|
||||
float ny = (byte) (packedNormal >>> 8 & 255) / 127f;
|
||||
float nz = (byte) (packedNormal >>> 16 & 255) / 127f;
|
||||
normal.set(nx, ny, nz).mul(normalMat);
|
||||
int color = template.color(i);
|
||||
float u = template.u(i);
|
||||
float v = template.v(i);
|
||||
int vertexOverlay = customOverlay ? overlay : template.overlay(i);
|
||||
int vertexLight = template.light(i);
|
||||
if (customLight) vertexLight = SuperByteBuffer.maxLight(vertexLight, light);
|
||||
consumer.addVertex(position.x, position.y, position.z)
|
||||
.setColor(color & 255, color >>> 8 & 255, color >>> 16 & 255, color >>> 24)
|
||||
.setUv(u, v)
|
||||
.setOverlay(vertexOverlay)
|
||||
.setLight(vertexLight)
|
||||
.setNormal(normal.x, normal.y, normal.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
client/src/main/java/dev/quorate/client/render/FaceCull.java
Normal file
84
client/src/main/java/dev/quorate/client/render/FaceCull.java
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package dev.quorate.client.render;
|
||||
|
||||
import net.createmod.catnip.render.TemplateMesh;
|
||||
import net.irisshaders.iris.layer.OuterWrappedRenderType;
|
||||
import net.irisshaders.iris.uniforms.CapturedRenderingState;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.RenderStateShard;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
public final class FaceCull {
|
||||
private FaceCull() {}
|
||||
|
||||
private static final class Types {
|
||||
private static final Class<?> COMPOSITE;
|
||||
private static final VarHandle STATE;
|
||||
private static final VarHandle CULL;
|
||||
private static final VarHandle ENABLED;
|
||||
|
||||
static {
|
||||
try {
|
||||
COMPOSITE =
|
||||
Class.forName(
|
||||
"net.minecraft.client.renderer.RenderType$CompositeRenderType");
|
||||
STATE =
|
||||
MethodHandles.privateLookupIn(COMPOSITE, MethodHandles.lookup())
|
||||
.findVarHandle(COMPOSITE, "state", RenderType.CompositeState.class);
|
||||
CULL =
|
||||
MethodHandles.privateLookupIn(
|
||||
RenderType.CompositeState.class, MethodHandles.lookup())
|
||||
.findVarHandle(
|
||||
RenderType.CompositeState.class,
|
||||
"cullState",
|
||||
RenderStateShard.CullStateShard.class);
|
||||
ENABLED =
|
||||
MethodHandles.privateLookupIn(
|
||||
RenderStateShard.BooleanStateShard.class,
|
||||
MethodHandles.lookup())
|
||||
.findVarHandle(
|
||||
RenderStateShard.BooleanStateShard.class,
|
||||
"enabled",
|
||||
boolean.class);
|
||||
} catch (ReflectiveOperationException failure) {
|
||||
throw new ExceptionInInitializerError(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean culls(RenderType type) {
|
||||
while (type instanceof OuterWrappedRenderType outer) type = outer.unwrap();
|
||||
return Types.COMPOSITE.isInstance(type)
|
||||
&& (boolean) Types.ENABLED.get(Types.CULL.get(Types.STATE.get(type)));
|
||||
}
|
||||
|
||||
public static PlaneBound.Selection plan(
|
||||
TemplateMesh template, Matrix4f matrix, int count, int prefix) {
|
||||
if (!ShaderInputs.current().cull()
|
||||
|| count < 1024
|
||||
|| count % 4 != 0
|
||||
|| prefix % 4 != 0
|
||||
|| count != template.vertexCount()
|
||||
|| (matrix.properties() & Matrix4f.PROPERTY_AFFINE) == 0) return null;
|
||||
var model = ((PlaneTarget) template).plane();
|
||||
var captured = CapturedRenderingState.INSTANCE;
|
||||
var window = Minecraft.getInstance().getWindow();
|
||||
var view =
|
||||
FastBound.view(
|
||||
model,
|
||||
matrix,
|
||||
captured.getGbufferModelView(),
|
||||
captured.getGbufferProjection(),
|
||||
window.getWidth(),
|
||||
window.getHeight(),
|
||||
ShaderInputs.current().pixelUnit(),
|
||||
ShaderInputs.TAA_JITTER);
|
||||
if (!view.active()) return null;
|
||||
var selected = PlaneBound.select(model, view);
|
||||
return selected.count() * 4 == count ? null : selected;
|
||||
}
|
||||
}
|
||||
31
client/src/main/java/dev/quorate/client/render/FaceRead.java
Normal file
31
client/src/main/java/dev/quorate/client/render/FaceRead.java
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package dev.quorate.client.render;
|
||||
|
||||
import net.irisshaders.iris.vertices.NormalHelper;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
public final class FaceRead {
|
||||
private FaceRead() {}
|
||||
|
||||
public static void compute(Vector3f output, long pointer, long[] offsets) {
|
||||
long a = pointer + offsets[0];
|
||||
long b = pointer + offsets[1];
|
||||
long c = pointer + offsets[2];
|
||||
long d = pointer + offsets[3];
|
||||
NormalHelper.computeFaceNormalManual(
|
||||
output,
|
||||
MemoryUtil.memGetFloat(a),
|
||||
MemoryUtil.memGetFloat(a + 4),
|
||||
MemoryUtil.memGetFloat(a + 8),
|
||||
MemoryUtil.memGetFloat(b),
|
||||
MemoryUtil.memGetFloat(b + 4),
|
||||
MemoryUtil.memGetFloat(b + 8),
|
||||
MemoryUtil.memGetFloat(c),
|
||||
MemoryUtil.memGetFloat(c + 4),
|
||||
MemoryUtil.memGetFloat(c + 8),
|
||||
MemoryUtil.memGetFloat(d),
|
||||
MemoryUtil.memGetFloat(d + 4),
|
||||
MemoryUtil.memGetFloat(d + 8));
|
||||
}
|
||||
}
|
||||
216
client/src/main/java/dev/quorate/client/render/FastBound.java
Normal file
216
client/src/main/java/dev/quorate/client/render/FastBound.java
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
package dev.quorate.client.render;
|
||||
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
public final class FastBound {
|
||||
// Four-term double FMAs need four rounding steps; cofactors need five per signed triple
|
||||
// product.
|
||||
private static final double DOT = gamma(4, 0x1p-53);
|
||||
private static final double MINOR = gamma(5, 0x1p-53);
|
||||
// CPU float products use seven steps; GLSL's one-ULP allowance doubles that for each of two
|
||||
// transforms.
|
||||
private static final double CPU = gamma(7, 0x1p-24);
|
||||
private static final double GPU = gamma(28, 0x1p-24);
|
||||
// Shader jitter allows five division steps and two each for multiplication and addition.
|
||||
private static final double JITTER = gamma(9, 0x1p-24);
|
||||
|
||||
private record Matrix(double[] values, double[] errors) {}
|
||||
|
||||
private FastBound() {}
|
||||
|
||||
private static double add(double a, double b) {
|
||||
return Math.nextUp(a + b);
|
||||
}
|
||||
|
||||
private static double mul(double a, double b) {
|
||||
return Math.nextUp(a * b);
|
||||
}
|
||||
|
||||
private static double gamma(int count, double unit) {
|
||||
double value = count * unit;
|
||||
return Math.nextUp(value / (1 - value));
|
||||
}
|
||||
|
||||
private static double[] values(Matrix4fc source) {
|
||||
var result = new double[16];
|
||||
for (int row = 0; row < 4; row++)
|
||||
for (int column = 0; column < 4; column++)
|
||||
result[row * 4 + column] = source.get(column, row);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Matrix product(Matrix a, double[] b) {
|
||||
var values = new double[16];
|
||||
var errors = new double[16];
|
||||
for (int row = 0; row < 4; row++)
|
||||
for (int col = 0; col < 4; col++) {
|
||||
int at = row * 4;
|
||||
values[at + col] =
|
||||
Math.fma(
|
||||
a.values()[at],
|
||||
b[col],
|
||||
Math.fma(
|
||||
a.values()[at + 1],
|
||||
b[4 + col],
|
||||
Math.fma(
|
||||
a.values()[at + 2],
|
||||
b[8 + col],
|
||||
a.values()[at + 3] * b[12 + col])));
|
||||
double norm = 0;
|
||||
double carried = 0;
|
||||
for (int k = 0; k < 4; k++) {
|
||||
norm = add(norm, mul(Math.abs(a.values()[at + k]), Math.abs(b[k * 4 + col])));
|
||||
carried = add(carried, mul(a.errors()[at + k], Math.abs(b[k * 4 + col])));
|
||||
}
|
||||
errors[at + col] = add(carried, mul(DOT, norm));
|
||||
}
|
||||
return new Matrix(values, errors);
|
||||
}
|
||||
|
||||
private static double[] absolute(double[] matrix, double[] vector) {
|
||||
var result = new double[4];
|
||||
for (int row = 0; row < 4; row++) {
|
||||
double sum = 0;
|
||||
for (int col = 0; col < 4; col++)
|
||||
sum = add(sum, mul(Math.abs(matrix[row * 4 + col]), vector[col]));
|
||||
result[row] = sum;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double minor(double[] m, int i, int j, int k) {
|
||||
return m[i] * (m[4 + j] * m[12 + k] - m[4 + k] * m[12 + j])
|
||||
- m[j] * (m[4 + i] * m[12 + k] - m[4 + k] * m[12 + i])
|
||||
+ m[k] * (m[4 + i] * m[12 + j] - m[4 + j] * m[12 + i]);
|
||||
}
|
||||
|
||||
private static double basisError(Matrix matrix) {
|
||||
double maximum = 0;
|
||||
double error = 0;
|
||||
for (int row : new int[] {0, 1, 3})
|
||||
for (int col = 0; col < 4; col++) {
|
||||
maximum = Math.max(maximum, Math.abs(matrix.values()[row * 4 + col]));
|
||||
error = Math.max(error, matrix.errors()[row * 4 + col]);
|
||||
}
|
||||
double square = mul(maximum, maximum);
|
||||
double errorSquare = mul(error, error);
|
||||
double linear = mul(3, mul(error, square));
|
||||
double quadratic = mul(3, mul(errorSquare, maximum));
|
||||
double cubic = mul(errorSquare, error);
|
||||
double changed = mul(6, add(add(linear, quadratic), cubic));
|
||||
double arithmetic = mul(MINOR, mul(6, mul(square, maximum)));
|
||||
return add(changed, arithmetic);
|
||||
}
|
||||
|
||||
public static PlaneBound.View view(
|
||||
PlaneBound.Model model,
|
||||
Matrix4fc local,
|
||||
Matrix4fc camera,
|
||||
Matrix4fc projection,
|
||||
int width,
|
||||
int height,
|
||||
double pixelUnit,
|
||||
double jitterMaximum) {
|
||||
var p = values(projection);
|
||||
var v = values(camera);
|
||||
var m = values(local);
|
||||
var matrix = product(product(new Matrix(p, new double[16]), v), m);
|
||||
var a = matrix.values();
|
||||
var ae = matrix.errors();
|
||||
var coefficients =
|
||||
new double[] {
|
||||
minor(a, 1, 2, 3), -minor(a, 0, 2, 3), minor(a, 0, 1, 3), -minor(a, 0, 1, 2)
|
||||
};
|
||||
var basis =
|
||||
new PlaneCull.Basis(
|
||||
coefficients[0], coefficients[1], coefficients[2], coefficients[3]);
|
||||
double coefficientError = basisError(matrix);
|
||||
double scoreError = 0;
|
||||
double scoreNorm = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double magnitude = Math.abs(coefficients[i]);
|
||||
scoreError =
|
||||
add(scoreError, mul(model.planeError()[i], add(magnitude, coefficientError)));
|
||||
scoreError = add(scoreError, mul(model.planeAbs()[i], coefficientError));
|
||||
scoreNorm = add(scoreNorm, mul(model.planeAbs()[i], magnitude));
|
||||
}
|
||||
scoreError = add(scoreError, mul(DOT, scoreNorm));
|
||||
var cpuNorm = absolute(m, model.magnitude());
|
||||
var cpuError = new double[4];
|
||||
var cpuTotal = new double[4];
|
||||
for (int row = 0; row < 4; row++) {
|
||||
cpuError[row] = mul(CPU, cpuNorm[row]);
|
||||
cpuTotal[row] = add(cpuNorm[row], cpuError[row]);
|
||||
}
|
||||
var carried = absolute(p, absolute(v, cpuError));
|
||||
var gpuNorm = absolute(p, absolute(v, cpuTotal));
|
||||
var error = new double[4];
|
||||
var maximum = new double[4];
|
||||
var gain = new double[4];
|
||||
double minimumW = 0;
|
||||
for (int row = 0; row < 4; row++) {
|
||||
error[row] = add(carried[row], mul(GPU, gpuNorm[row]));
|
||||
double low = 0;
|
||||
double high = 0;
|
||||
double matrixError = 0;
|
||||
for (int column = 0; column < 4; column++) {
|
||||
int at = row * 4 + column;
|
||||
double first = a[at] * model.low()[column];
|
||||
double last = a[at] * model.high()[column];
|
||||
low = Math.nextDown(low + Math.nextDown(Math.min(first, last)));
|
||||
high = Math.nextUp(high + Math.nextUp(Math.max(first, last)));
|
||||
matrixError = add(matrixError, mul(ae[at], model.magnitude()[column]));
|
||||
if (column != 3 || model.low()[3] != model.high()[3])
|
||||
gain[row] = Math.max(gain[row], add(Math.abs(a[at]), ae[at]));
|
||||
}
|
||||
low = Math.nextDown(low - matrixError);
|
||||
high = Math.nextUp(high + matrixError);
|
||||
maximum[row] = Math.max(Math.abs(low), Math.abs(high));
|
||||
if (row == 3) minimumW = low;
|
||||
}
|
||||
for (int axis = 0; axis < 2; axis++) {
|
||||
double size = axis == 0 ? width : height;
|
||||
if (jitterMaximum != 0) {
|
||||
double jitter = Math.nextUp(jitterMaximum / size);
|
||||
double amount = mul(maximum[3], jitter);
|
||||
error[axis] =
|
||||
add(
|
||||
add(error[axis], mul(error[3], jitter)),
|
||||
mul(add(maximum[axis], amount), JITTER));
|
||||
maximum[axis] = add(maximum[axis], amount);
|
||||
gain[axis] = add(gain[axis], mul(gain[3], jitter));
|
||||
}
|
||||
double raster = Math.nextUp(mul(2, pixelUnit) / size);
|
||||
error[axis] = add(error[axis], mul(add(maximum[3], error[3]), raster));
|
||||
}
|
||||
double x = maximum[0];
|
||||
double y = maximum[1];
|
||||
double w = maximum[3];
|
||||
double ex = error[0];
|
||||
double ey = error[1];
|
||||
double ew = error[3];
|
||||
double rx = gain[0];
|
||||
double ry = gain[1];
|
||||
double rw = gain[3];
|
||||
double linear =
|
||||
mul(
|
||||
3,
|
||||
add(
|
||||
add(
|
||||
mul(ex, add(mul(y, rw), mul(w, ry))),
|
||||
mul(ey, add(mul(x, rw), mul(w, rx)))),
|
||||
mul(ew, add(mul(x, ry), mul(y, rx)))));
|
||||
double constant =
|
||||
mul(
|
||||
6,
|
||||
add(
|
||||
add(mul(mul(ex, ey), w), mul(mul(ex, ew), y)),
|
||||
add(mul(mul(ey, ew), x), mul(mul(ex, ey), ew))));
|
||||
constant = add(constant, scoreError);
|
||||
return new PlaneBound.View(
|
||||
basis,
|
||||
linear,
|
||||
constant,
|
||||
minimumW > error[3] && Double.isFinite(linear) && Double.isFinite(constant));
|
||||
}
|
||||
}
|
||||
40
client/src/main/java/dev/quorate/client/render/MeshPlan.java
Normal file
40
client/src/main/java/dev/quorate/client/render/MeshPlan.java
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package dev.quorate.client.render;
|
||||
|
||||
import net.createmod.catnip.render.TemplateMesh;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class MeshPlan {
|
||||
private final int[] data;
|
||||
private final PlaneBound.Model model;
|
||||
private final TemplateMesh snapshot;
|
||||
|
||||
public MeshPlan(int[] source) {
|
||||
data = source.clone();
|
||||
snapshot = new TemplateMesh(data);
|
||||
int count = data.length / 9;
|
||||
var positions = new float[count * 4];
|
||||
var indices = new int[count / 4 * 6];
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int axis = 0; axis < 3; axis++)
|
||||
positions[i * 4 + axis] = Float.intBitsToFloat(data[i * 9 + axis]);
|
||||
positions[i * 4 + 3] = 1;
|
||||
}
|
||||
int[] order = {0, 1, 2, 2, 3, 0};
|
||||
for (int quad = 0; quad < count / 4; quad++)
|
||||
for (int i = 0; i < 6; i++) indices[quad * 6 + i] = quad * 4 + order[i];
|
||||
model = PlaneBound.build(positions, indices);
|
||||
}
|
||||
|
||||
public boolean matches(int[] source) {
|
||||
return Arrays.equals(data, source);
|
||||
}
|
||||
|
||||
public TemplateMesh snapshot() {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public PlaneBound.Model model() {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
132
client/src/main/java/dev/quorate/client/render/PlaneBound.java
Normal file
132
client/src/main/java/dev/quorate/client/render/PlaneBound.java
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
package dev.quorate.client.render;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class PlaneBound {
|
||||
public record Model(
|
||||
PlaneCull.Planes planes,
|
||||
double[] edges,
|
||||
double[] low,
|
||||
double[] high,
|
||||
double[] magnitude,
|
||||
double[] planeAbs,
|
||||
double[] planeError) {}
|
||||
|
||||
public record View(PlaneCull.Basis basis, double linear, double constant, boolean active) {
|
||||
public double limit(Model model, int face) {
|
||||
return Math.nextUp(Math.fma(linear, model.edges()[face], constant));
|
||||
}
|
||||
}
|
||||
|
||||
public record Selection(int[] starts, int count) {}
|
||||
|
||||
record Span(double low, double high) {
|
||||
static Span of(double value) {
|
||||
return new Span(value, value);
|
||||
}
|
||||
|
||||
Span add(Span other) {
|
||||
return new Span(Math.nextDown(low + other.low), Math.nextUp(high + other.high));
|
||||
}
|
||||
|
||||
Span sub(Span other) {
|
||||
return new Span(Math.nextDown(low - other.high), Math.nextUp(high - other.low));
|
||||
}
|
||||
|
||||
Span mul(Span other) {
|
||||
double a = low * other.low;
|
||||
double b = low * other.high;
|
||||
double c = high * other.low;
|
||||
double d = high * other.high;
|
||||
return new Span(
|
||||
Math.nextDown(Math.min(Math.min(a, b), Math.min(c, d))),
|
||||
Math.nextUp(Math.max(Math.max(a, b), Math.max(c, d))));
|
||||
}
|
||||
|
||||
Span scale(double value) {
|
||||
return mul(of(value));
|
||||
}
|
||||
|
||||
double absolute() {
|
||||
return Math.max(Math.abs(low), Math.abs(high));
|
||||
}
|
||||
|
||||
double error(double value) {
|
||||
return Math.nextUp(Math.max(Math.abs(low - value), Math.abs(high - value)));
|
||||
}
|
||||
}
|
||||
|
||||
private PlaneBound() {}
|
||||
|
||||
private static Span minor(Span[][] data, int i, int j, int k) {
|
||||
return data[0][i]
|
||||
.mul(data[1][j].mul(data[2][k]).sub(data[1][k].mul(data[2][j])))
|
||||
.sub(data[0][j].mul(data[1][i].mul(data[2][k]).sub(data[1][k].mul(data[2][i]))))
|
||||
.add(data[0][k].mul(data[1][i].mul(data[2][j]).sub(data[1][j].mul(data[2][i]))));
|
||||
}
|
||||
|
||||
private static Span[] cofactors(Span[][] rows) {
|
||||
return new Span[] {
|
||||
minor(rows, 1, 2, 3),
|
||||
minor(rows, 0, 2, 3).scale(-1),
|
||||
minor(rows, 0, 1, 3),
|
||||
minor(rows, 0, 1, 2).scale(-1)
|
||||
};
|
||||
}
|
||||
|
||||
public static Model build(float[] vertices, int[] indices) {
|
||||
var planes = PlaneCull.build(vertices, indices);
|
||||
var low = new double[4];
|
||||
var high = new double[4];
|
||||
Arrays.fill(low, Double.POSITIVE_INFINITY);
|
||||
Arrays.fill(high, Double.NEGATIVE_INFINITY);
|
||||
for (int at = 0; at < vertices.length; at += 4)
|
||||
for (int axis = 0; axis < 4; axis++) {
|
||||
low[axis] = Math.min(low[axis], vertices[at + axis]);
|
||||
high[axis] = Math.max(high[axis], vertices[at + axis]);
|
||||
}
|
||||
var magnitude = new double[4];
|
||||
for (int axis = 0; axis < 4; axis++)
|
||||
magnitude[axis] = Math.max(Math.abs(low[axis]), Math.abs(high[axis]));
|
||||
var edges = new double[indices.length / 3];
|
||||
var maximum = new double[4];
|
||||
var error = new double[4];
|
||||
double[][] coefficients = {planes.x(), planes.y(), planes.z(), planes.w()};
|
||||
for (int face = 0; face < edges.length; face++) {
|
||||
var rows = new Span[3][4];
|
||||
for (int p = 0; p < 3; p++)
|
||||
for (int axis = 0; axis < 4; axis++)
|
||||
rows[p][axis] = Span.of(vertices[indices[face * 3 + p] * 4 + axis]);
|
||||
var bounds = cofactors(rows);
|
||||
for (int axis = 0; axis < 4; axis++) {
|
||||
maximum[axis] = Math.max(maximum[axis], Math.abs(coefficients[axis][face]));
|
||||
error[axis] = Math.max(error[axis], bounds[axis].error(coefficients[axis][face]));
|
||||
}
|
||||
for (int p = 0; p < 3; p++) {
|
||||
var sum = Span.of(0);
|
||||
for (int axis = 0; axis < 4; axis++)
|
||||
sum = sum.add(Span.of(rows[p][axis].sub(rows[(p + 1) % 3][axis]).absolute()));
|
||||
edges[face] = Math.max(edges[face], sum.high());
|
||||
}
|
||||
}
|
||||
return new Model(planes, edges, low, high, magnitude, maximum, error);
|
||||
}
|
||||
|
||||
public static boolean back(Model model, int face, View view) {
|
||||
return view.active()
|
||||
&& (model.planes().repeated()[face]
|
||||
|| PlaneCull.score(model.planes(), face, view.basis())
|
||||
< -view.limit(model, face));
|
||||
}
|
||||
|
||||
public static Selection select(Model model, View view) {
|
||||
var starts = new int[model.edges().length / 2];
|
||||
int count = 0;
|
||||
for (int face = 0; face < model.edges().length; face += 2)
|
||||
if (face == 0
|
||||
|| face + 2 == model.edges().length
|
||||
|| !back(model, face, view)
|
||||
|| !back(model, face + 1, view)) starts[count++] = face * 2;
|
||||
return new Selection(starts, count);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package dev.quorate.client.render;
|
||||
|
||||
public final class PlaneCull {
|
||||
public record Planes(
|
||||
double[] x, double[] y, double[] z, double[] w, boolean[] repeated, int[] indices) {}
|
||||
|
||||
public record Basis(double x, double y, double z, double w) {}
|
||||
|
||||
private PlaneCull() {}
|
||||
|
||||
private static double minor(float[] data, int a, int b, int c, int i, int j, int k) {
|
||||
double x = data[a + i];
|
||||
double y = data[a + j];
|
||||
double z = data[a + k];
|
||||
double u = data[b + i];
|
||||
double v = data[b + j];
|
||||
double w = data[b + k];
|
||||
double p = data[c + i];
|
||||
double q = data[c + j];
|
||||
double r = data[c + k];
|
||||
return x * (v * r - w * q) - y * (u * r - w * p) + z * (u * q - v * p);
|
||||
}
|
||||
|
||||
private static boolean same(float[] data, int a, int b) {
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (Float.floatToRawIntBits(data[a + i]) != Float.floatToRawIntBits(data[b + i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Planes build(float[] vertices, int[] indices) {
|
||||
int count = indices.length / 3;
|
||||
var x = new double[count];
|
||||
var y = new double[count];
|
||||
var z = new double[count];
|
||||
var w = new double[count];
|
||||
var repeated = new boolean[count];
|
||||
for (int face = 0; face < count; face++) {
|
||||
int a = indices[face * 3] * 4;
|
||||
int b = indices[face * 3 + 1] * 4;
|
||||
int c = indices[face * 3 + 2] * 4;
|
||||
x[face] = minor(vertices, a, b, c, 1, 2, 3);
|
||||
y[face] = -minor(vertices, a, b, c, 0, 2, 3);
|
||||
z[face] = minor(vertices, a, b, c, 0, 1, 3);
|
||||
w[face] = -minor(vertices, a, b, c, 0, 1, 2);
|
||||
repeated[face] = same(vertices, a, b) || same(vertices, b, c) || same(vertices, c, a);
|
||||
}
|
||||
return new Planes(x, y, z, w, repeated, indices.clone());
|
||||
}
|
||||
|
||||
public static double score(Planes planes, int face, Basis basis) {
|
||||
return Math.fma(
|
||||
planes.x()[face],
|
||||
basis.x(),
|
||||
Math.fma(
|
||||
planes.y()[face],
|
||||
basis.y(),
|
||||
Math.fma(planes.z()[face], basis.z(), planes.w()[face] * basis.w())));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue