Add client rendering and collision optimizations with parity tests
This commit is contained in:
parent
28494403f2
commit
5c04905561
47 changed files with 3167 additions and 0 deletions
14
NOTICE.md
14
NOTICE.md
|
|
@ -6,6 +6,8 @@
|
||||||
| [Mekanism 10.7.19.85](https://github.com/mekanism/Mekanism/tree/a00109e4856fd38b9c5b3dd7f22ce4a59cd65a80) | MIT | `SlotMemo`, `FlowBatch`, `InvMixin`, and `MekMixin` adapt `TransporterManager` inventory prediction. Changes dated 2026-10-09 memoize slot validation within one prediction snapshot and eliminate a repeated stack compatibility check. |
|
| [Mekanism 10.7.19.85](https://github.com/mekanism/Mekanism/tree/a00109e4856fd38b9c5b3dd7f22ce4a59cd65a80) | MIT | `SlotMemo`, `FlowBatch`, `InvMixin`, and `MekMixin` adapt `TransporterManager` inventory prediction. Changes dated 2026-10-09 memoize slot validation within one prediction snapshot and eliminate a repeated stack compatibility check. |
|
||||||
| [Functional Storage 1.5.8](https://www.curseforge.com/minecraft/mc-mods/functional-storage/files/8459097), by Buuz135 and Rid | MIT | `MemberList`, `DrawersMixin`, and `SetupMixin` index handler membership and remove stream overhead. Modifications dated 2026-10-16. |
|
| [Functional Storage 1.5.8](https://www.curseforge.com/minecraft/mc-mods/functional-storage/files/8459097), by Buuz135 and Rid | MIT | `MemberList`, `DrawersMixin`, and `SetupMixin` index handler membership and remove stream overhead. Modifications dated 2026-10-16. |
|
||||||
| [Refined Storage 2.0.9](https://modrinth.com/mod/refined-storage/version/lHHiI26k), by Refined Mods | MIT | `PlanRows` and `PlanMixin` detach shared ingredient rows before writing, preserving branch isolation. Modifications dated 2026-10-16. |
|
| [Refined Storage 2.0.9](https://modrinth.com/mod/refined-storage/version/lHHiI26k), by Refined Mods | MIT | `PlanRows` and `PlanMixin` detach shared ingredient rows before writing, preserving branch isolation. Modifications dated 2026-10-16. |
|
||||||
|
| [Ponder 1.0.82](https://github.com/Creators-of-Create/Ponder), by The Create Team | MIT | `ColorDraw` and `BatchDraw` adapt Catnip template emission. Modifications dated 2026-10-19 fuse buffer writes and omit unused shader attributes. |
|
||||||
|
| [Iris 1.8.12](https://github.com/IrisShaders/Iris), by coderbot and IMS212 | LGPL-3.0-only | `BatchDraw` and `QuadEnd` adapt extended quad completion. Modifications dated 2026-10-19 preserve deferred completion while skipping unconsumed attributes. |
|
||||||
|
|
||||||
## Mekanism license
|
## Mekanism license
|
||||||
|
|
||||||
|
|
@ -40,3 +42,15 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
## Ponder license
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 The Create Team
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,31 @@
|
||||||
version = '0.1.0'
|
version = '0.1.0'
|
||||||
base.archivesName = 'saltatory-client'
|
base.archivesName = 'saltatory-client'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url = 'https://maven.createmod.net'
|
||||||
|
content {
|
||||||
|
includeGroup 'net.createmod.ponder'
|
||||||
|
includeGroup 'dev.engine-room.flywheel'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url = 'https://api.modrinth.com/maven'
|
||||||
|
content { includeGroup 'maven.modrinth' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations { testImplementation.extendsFrom compileOnly }
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly('net.createmod.ponder:ponder-neoforge:1.0.82+mc1.21.1') { transitive = false }
|
||||||
|
compileOnly('maven.modrinth:iris:t3ruzodq') { transitive = false }
|
||||||
|
compileOnly('dev.engine-room.flywheel:flywheel-neoforge-1.21.1:1.0.6') { transitive = false }
|
||||||
|
testImplementation 'org.ow2.asm:asm-tree:9.8'
|
||||||
|
testImplementation 'org.ow2.asm:asm-commons:9.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
workingDir layout.buildDirectory.dir('test-work')
|
||||||
|
doFirst { workingDir.mkdirs() }
|
||||||
|
}
|
||||||
|
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue